Cast entities implicitly with EF Core - c#

Suppose I have following entities
public abstract class BaseEntity {
public Guid Id { get;set; }
public string Prop1 { get;set; }
public long Prop2 { get;set; }
public byte Type_Id { get;set; }
}
public class Type1 : BaseEntity { }
public class Type2 : BaseEntity { }
public class Type3 : BaseEntity {
public long? Prop3 { get;set; }
}
And following context mapping:
builder.ToTable("Entities").HasDiscriminator(a => a.Type_Id)
.HasValue<Type1>((byte)Types.Type1)
.HasValue<Type2>((byte)Types.Type2)
.HasValue<Type3>((byte)Types.Type3);
// in DbContext
public DbSet<BaseEntity> Entities { get; set; }
I want to create get IQueryable from DB (all records) Type1 and Type2 will have null in Prop3 ,
I do the following:
public DbSet<BaseEntity> DBSet { get;set; }
private static readonly MethodInfo FromSqlMethodInfo = typeof(RelationalQueryableExtensions).GetTypeInfo().GetDeclaredMethods("FromSql").Single(mi => mi.GetParameters().Length == 3);
public IQueryable<Type3> GetEntities(IEnumerable<Guid> ids) {
RawSqlString sql = #"select [Id]
,[Prop1]
,[Prop2]
,[Prop3]
,[Type_Id]
from [dbo].[Entities] where Id in (select item from #Ids)";
var ids = new SqlParameter("#Ids", SqlDbType.Structured);
ids.TypeName = typeof(Guid).Name.ToLowerInvariant() + "_item_list";
ids.Direction = ParameterDirection.Input;
ids.Value = CreateItemList(tpIds);
var param = new object[] { ids };
var conversion = from s in DBSet select (Type3)s;
var result = conversion.Provider.CreateQuery<Type3>(Expression.Call(null, FromSqlMethodInfo.MakeGenericMethod(typeof(Type3)), conversion.Expression,
Expression.Constant(sql), Expression.Constant(param)));
return result;
}
var query = GetEntities(someIds);
var result = query.OrderBy(m => m.Type_Id).Skip(skip).Take(take).ToList();
And query executes successfully, but when ToLIst is called, Exception is thrown:
Unable to cast object of type 'Type1' to type 'Type3', which is quite expected, since we not telling how it should be converted...so the question is: can such trick be done with EF Core ?

Ok, so for now only possible solution is to create one more entity with all properties required,
public class NewEntity {
public Guid Id { get;set; }
public string Prop1 { get;set; }
public long Prop2 { get;set; }
public byte Type_Id { get;set; }
public long? Prop3 { get;set; }
}
add new DbSet to context
public DbSet<NewEntity> NewEntities { get; set; }
Map it to old table and update context configuration properly,
to avoid redundant columns generation (in this scenario EF will probaly
try to add new columns with names NewEntity_Prop1 , NewEntity_Prop2 etc..
as well as Foreign keys and indexes
public override void Configure(EntityTypeBuilder<NewEntity> builder) {
builder.ToTable("Entities");
builder.Property(m => m.Prop1).HasColumnName("Prop1");
builder.Property(m => m.Prop2).HasColumnName("Prop2");
builder.Property(m => m.Type_Id).HasColumnName("Type_Id");
builder.HasOne<BaseEntity>().WithOne().HasForeignKey<NewEntity>(e => e.Id);
}
Or create View
public DbQuery<NewEntity> NewEntities { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Query<NewEntity>().ToView("NewEntities");
base.OnModelCreating(modelBuilder);
}

You can use implicit Overload Cast Operators. Here is a link to help you.

Related

Many to many to another many to many on single entity

I have fairly simple question about many to many relationships using entity framework.
Situation looks like this I have 3 models SectionName:
public class SectionName : BaseEntity
{
public SectionName()
{
SectionsSuffix = new List<SectionSuffix>();
}
[Required]
public string Name { get; set; }
public ICollection<SectionSuffix> SectionsSuffix { get; set; }
}
Section Suffix:
[Table("SectionsSuffix")]
public class SectionSuffix : BaseEntity
{
public SectionSuffix()
{
SectionLines = new List<SectionLine>();
SectionsName = new List<SectionName>();
}
[Required]
public string Name { get; set; }
public ICollection<SectionLine> SectionLines { get; set; }
public ICollection<SectionName> SectionsName { get; set; }
}
And SectionLines:
[Table("SectionLines")]
public class SectionLine : BaseEntity
{
public SectionLine()
{
SectionsSuffix = new List<SectionSuffix>();
}
[Required]
public string Name { get; set; }
public ICollection<SectionSuffix> SectionsSuffix { get; set; }
}
Now SectionsName is related by many to many to SectionsSuffix and that is related with many to many to SectionLines, in context using FluentApi and junction tables:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<SectionName>()
.HasMany(suffix => suffix.SectionsSuffix)
.WithMany(name => name.SectionsName)
.Map(nameSuffix =>
{
nameSuffix.ToTable("SectionsNameSuffix");
nameSuffix.MapLeftKey("SectionNameId");
nameSuffix.MapRightKey("SectionSuffixId");
});
modelBuilder.Entity<SectionSuffix>()
.HasMany(line => line.SectionLines)
.WithMany(suffix => suffix.SectionsSuffix)
.Map(nameSuffix =>
{
nameSuffix.ToTable("SectionsSuffixLines");
nameSuffix.MapLeftKey("SectionSuffixId");
nameSuffix.MapRightKey("SectionLinesId");
});
}
Now, if with no problem when I call SectionsNames I can get SectionsSuffix list, I want to with this one call get also list of SectionNames binged to specific SectionSuffix, is it possible?
For now using repository pattern procedure looks like this:
IList<SectionName> sections = SectionRepository.GetAll(x => x.SectionsSuffix).ToList();
public virtual IEnumerable<T> GetAll(params Expression<Func<T, object>>[] includes)
{
IQueryable query = includes.Aggregate(_dbSet.AsQueryable(), (current, include) => current.Include(include));
return (IEnumerable<T>) query;
}
If I would query sth. like you want to do, it would be like that:
using System;
using System.Data.Entity;
public class SectionRepository
{
private readonly _context;
public SectionRepository(IMyDbContext context)
{
_context = context
}
public ICollection<SectionName> GetAll()
{
return _context.SectionNames
.Include(sn => sn.SectionsSuffix.SectionLine)
.Select(sn => sn).ToList();
}
}
Please have a try, the code above is not tested.
Answer was fairly simple, i would need to use:
IList<SectionName> sections = SectionRepository.GetAll(name => name.SectionsSuffix,
name => name.SectionsSuffix.Select(suffix => suffix.SectionLines)).ToList();
public virtual IEnumerable<T> GetAll(params Expression<Func<T, object>>[] includes)
{
IQueryable query = includes.Aggregate(_dbSet.AsQueryable(), (current, include) => current.Include(include));
return (IEnumerable<T>) query;
}

How update object with relation data in EntityFramework Core

I have used the Entity Framework Core and update some object with relation to another object.
I have a following entities:
Client:
public class Client
{
public int Id { get; set; }
public string ClientId { get; set; }
public string ClientName { get; set; }
public List<ClientScope> Scopes { get; set; }
}
ClientScope:
public class ClientScope
{
public int Id { get; set; }
public string Scope { get; set; }
public Client Client { get; set; }
}
OnModelCreating:
modelBuilder.Entity<Client>((Action<EntityTypeBuilder<Client>>) (client =>
{
client.ToTable<Client>(storeOptions.Client);
client.HasKey((Expression<Func<Client, object>>) (x => (object) x.Id));
client.Property<string>((Expression<Func<Client, string>>) (x => x.ClientId)).HasMaxLength(200).IsRequired(true);
client.HasIndex((Expression<Func<Client, object>>) (x => x.ClientId)).IsUnique(true);
client.HasMany<ClientScope>((Expression<Func<Client, IEnumerable<ClientScope>>>) (x => x.AllowedScopes)).WithOne((Expression<Func<ClientScope, Client>>) (x => x.Client)).IsRequired(true).OnDelete(DeleteBehavior.Cascade);
}));
modelBuilder.Entity<ClientScope>((Action<EntityTypeBuilder<ClientScope>>) (scope =>
{
scope.ToTable<ClientScope>(storeOptions.ClientScopes);
scope.Property<string>((Expression<Func<ClientScope, string>>) (x => x.Scope)).HasMaxLength(200).IsRequired(true);
}));
I would like update the ClientScope for specific Client.Id for example Id = 1.
I have tried use this way:
public void UpdateClientScope(ClientScope scope){
_dbContext.ClientScope.Update(scope);
_dbContext.SaveChanges();
}
var scope = new ClientScope() { Client = new Client{Id = 1}, Id = 1, Scope = "Test Scope" }
UpdateClientScope(scope);
But this way try to update the Client as well. I want to update only ClientScope and specify ClientId which it is stored on my form.
What is the best way how update ClientScope above?
I have tried to implement the BaseRepository which I want to implement for every entity something like this:
public class BaseRepository<TDbContext, TEntity, TPrimaryKey> : IBaseRepository<TDbContext, TEntity, TPrimaryKey>
where TEntity : class
where TDbContext : DbContext
{
public virtual DbSet<TEntity> Table => _dbContext.Set<TEntity>();
private readonly TDbContext _dbContext;
public BaseRepository(TDbContext dbContext)
{
_dbContext = dbContext;
}
public virtual async Task<TEntity> UpdateAsync(TEntity entity)
{
Table.Update(entity);
await _dbContext.SaveChangesAsync();
return entity;
}
}
But I don't know - How correctly specify the update method for entities like this?
Thank you for any suggestion.
If you never plan to add or modify a related entity via your repository methods you can simply set all other entities' state to EntityState.Unchanged, e.g.:
public virtual async Task<TEntity> UpdateAsync(TEntity entity)
{
Table.Update(entity);
foreach( var entry in _dbContext.ChangeTracker.Entries() )
{
if( entry.Entity != entity )
{
entry.State = EntityState.Unchanged;
}
}
await _dbContext.SaveChangesAsync();
return entity;
}
Alternatively, attach related entities as unchanged before calling the repo method(s). Perhaps create a repo method to return such an entity:
public TEntity GetEntityPlaceholder( int id )
{
var entity = new TEntity() { Id = id };
_dbContext.Attach( entity );
return entity;
}
I prefer having the FK properties available, myself:
public class ClientScope
{
public int Id { get; set; }
public string Scope { get; set; }
public Client Client { get; set; }
// FK, use [ForeignKey( "Client" )] if you wish
public int ClientId { get; set; }
}
// or use FluentAPI
modelBuilder.Entity<ClientScope>()
.HasRequired( cs => cs.Client )
.WithMany( c => c.Scopes )
// specify foreign key
.HasForeignKey( cs => cs.ClientId );
// now can specify a client by setting the ClientId property
var scope = new ClientScope()
{
ClientId = 1,
Id = 1,
Scope = "Test Scope",
}
UpdateClientScope(scope);
As mentioned in the comments (thanks, Ivan), EF needs to 'know about' the object you want to update.
Sorry, I don't have anything to hand to test this with, but your UpdateClientScope method should look something like this:
public void UpdateClientScope(ClientScope scope){
// Get the existing object from the DB
ClientScope dbScope = _dbContext.ClientScope.FirstOrDefault(x => x.Id == scope.Id);
// Test it was in DB
if (dbScope != null)
{
// Update the database object
dbScope.Scope = scope.Scope;
dbScope.Client = scope.Client;
// SaveChanges works on dbScope
_dbContext.SaveChanges();
}
else
{
// Object not found, some error processing
}
}
I do not understand where is a problem ? through DBContext object you can reach any class object than object's property, than just change a value of property with simple assign operator and just DbContext.SaveChanges() your changes will be updated in Database column with no problem at all. If you want separate Update() method for changes you can do it with couple of code lines or more complex way with C# Reflection possibility but i can't imagine why would you need this ?! If i missed something tell me more. Good luck !!!!
i think your entities false because your ClientScope has not clientId (Foreign Key).
public string ClientId { get; set; }

Using automapper to update entities with relationships

I'm using EF Core and Automapper to update my entities. I have an entity called "Aluno", which has a relationship to "Endereco". Here is the code:
public class Aluno : ApplicationUser{
...
public Endereco Endereco { get; set; }
...
}
public class AlunoViewModel : UserViewModel
{
public int Id { get; set; }
public EnderecoViewModel Endereco { get; set; }
}
public class Endereco{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get;set; }
...
}
public class EnderecoViewModel {
public int Id { get;set; }
...
}
The automapper configuration:
config.CreateMap<Aluno, AlunoViewModel>().ReverseMap();
config.CreateMap<Endereco, EnderecoViewModel>().ReverseMap();
AlunosController:
[HttpPut]
public JsonResult Put([FromBody]AlunoViewModel vm)
{
if (ModelState.IsValid)
{
var aluno = _context.Alunos
.Single(o => o.Id == vm.Id);
Mapper.Map(vm, aluno);
_context.SaveChanges();
Response.StatusCode = (int)System.Net.HttpStatusCode.OK;
return Json(true);
}
Response.StatusCode = (int)System.Net.HttpStatusCode.BadRequest;
return Json(false);
}
When I try to update an "Aluno" entity, I'm getting an exception on context.SaveChanges():
Cannot insert explicit value for identity column in table 'Enderecos'
when IDENTITY_INSERT is set to OFF.
But i'm not trying to insert a new "Endereco", just update the entity "Aluno", and maybe update the "Endereco", which is loaded correctly inside the var aluno.
Am I doing something wrong?
Try the following syntax:
Mapper
.CreateMap<Aluno, AlunoViewModel>()
.ForMember(
dest => dest.Endereco,
opt => opt.MapFrom(src => src.EnderecoViewModel)
);

How to avoid N+1 in EF generated queries

I used to generate all the helper tables like UsergroupUsers for many-to-many relations or relational Ids in POCO myself but now I want EF to take care of them. Now I don't think it's such a good idea after all.
Problem
When I try to get all UsergroupDynamicField for particular user it generates N+1 query for every usergroup user is in.
Here I overcommed this problem by simply stating that Usergroups will be IQUeriable instead of IEnumerable. Now I cannot do that because EF won't map it, it has to be ICollection.
Code
public class User
{
...
public virtual ICollection<Usergroup> Usergroups { get; set; }
public IEnumerable<UserField> Fields
{
get
{
var fields = this.Usergroups.SelectMany(x => x.UsergroupDynamicFields); // N + 1 for every Usergroup
foreach (var field in fields)
{
yield return new UserField
{
Name = field.Name
};
}
}
}
}
Database
Here I overcommed this problem by simply stating that Usergroups will be IQUeriable instead of IEnumerable. Now I cannot do that because EF won't map it, it has to be ICollection.
But the class that ends up implementing ICollection is EntityCollection<T>. This collection has a CreateSourceQuery() function that you can use:
var usergroupsQuery = ((EntityCollection<UserGroup>)this.Usergroups).CreateSourceQuery();
var fields = usergroupsQuery.SelectMany(x => x.UsergroupDynamicFields);
Update: as pointed out in the comments, ICollection<T> will only be implemented using EntityCollection<T> when change tracking is possible and enabled (non-sealed classes, and all relevant properties virtual). You can create a query another way:
var usergroupsQuery = db.Entry(this).Collection(u => u.Usergroups).Query();
var fields = usergroupsQuery.SelectMany(x => x.UsergroupDynamicFields);
Note that this requires that you have access to db somehow.
I try with something like
var res = c.Users.Include("Groups.DynFields").First().Groups.SelectMany(x => x.DynFields).ToList();
and it seems to be ok. I use EF5.
Of course... this is not a method in the User class. It requires to be able to invoke Include method on a DbSet object.
I hope this may help.
full solution
public class User {
public Int32 Id { get; set; }
public String Name { get; set; }
public virtual ICollection<UserGroup> Groups { get; set; }
}
public class UserGroup {
public Int32 Id { get; set; }
public String Name { get; set; }
public virtual ICollection<User> Users { get; set; }
public virtual ICollection<UserGroupDynamicField> DynFields { get; set; }
}
public class UserGroupDynamicField {
public Int32 Id { get; set; }
public String Name { get; set; }
public virtual UserGroup Group { get; set; }
}
public class UserGroupDynFEFCFConfiguration : EntityTypeConfiguration<UserGroupDynamicField > {
public UserGroupDynFEFCFConfiguration()
: base() {
HasRequired(x => x.Group);
}
}
public class UserGroupEFCFConfiguration : EntityTypeConfiguration<UserGroup> {
public UserGroupEFCFConfiguration()
: base() {
HasMany(x => x.Users).WithMany(y => y.Groups);
}
}
public class TestEFContext : DbContext {
public IDbSet<User> Users { get; set; }
public IDbSet<UserGroup> Groups { get; set; }
public TestEFContext(String cs)
: base(cs) {
Database.SetInitializer<TestEFContext>(new DropCreateDatabaseAlways<TestEFContext>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new UserGroupDynFEFCFConfiguration());
modelBuilder.Configurations.Add(new UserGroupEFCFConfiguration());
}
}
class Program {
static void Main(String[] args) {
String cs = #"Data Source=ALIASTVALK;Initial Catalog=TestEF;Integrated Security=True; MultipleActiveResultSets=True";
using (TestEFContext c = new TestEFContext(cs)) {
UserGroup g1 = new UserGroup {
Name = "G1",
DynFields = new List<UserGroupDynamicField> {
new UserGroupDynamicField { Name = "DF11"},
new UserGroupDynamicField { Name = "DF12"}
}
};
c.Groups.Add(g1);
UserGroup g2 = new UserGroup {
Name = "G2",
DynFields = new List<UserGroupDynamicField> {
new UserGroupDynamicField { Name = "DF21"},
new UserGroupDynamicField { Name = "DF22"}
}
};
c.Groups.Add(g2);
c.Users.Add(new User {
Name = "U1",
Groups = new List<UserGroup> { g1, g2 }
});
c.SaveChanges();
}
using (TestEFContext c = new TestEFContext(cs)) {
var res = c.Users.Include("Groups.DynFields").First().Groups.SelectMany(x => x.DynFields).ToList();
foreach (var v in res) {
Console.WriteLine(v.Name);
}
}
}
}

null values in NHibernate returning list

I'm trying to understand the following behaviour of NHibernate and can't.
session.CreateCriteria<Param>()
.Add(Restrictions.Eq("GroupId", groupId))
.Add(Restrictions.Eq("ParamId", paramId))
.List<Param>()
This construction should return collection with 1 database row(object). But returns list with single null element. Could you suggest why?
P.S. Param class mapping has a composite key.
public class Param
{
public Param()
{
}
public Param(string groupId, string paramId, string paramName) : this()
{
GroupId = groupId;
ParamId = paramId;
ParamName = paramName;
}
public virtual string GroupId { get; protected set; }
public virtual string ParamId { get; protected set; }
public virtual string ParamName { get; protected set; }
public virtual string Value { get; set; }
public override bool Equals(object obj){...}
public override int GetHashCode(){...}
}
public class ParamMap : ClassMap<Param>
{
public ParamMap()
{
Table("Params");
CompositeId()
.KeyProperty(x => x.GroupId)
.KeyProperty(x => x.ParamId)
.KeyProperty(x => x.ParamName, "ParamName");
Map(x => x.Value, "ParamValue");
}
}
This query is copied from database log. NHibernate executes it.
SELECT THIS_.GROUPID AS GROUPID19_0_, THIS_.PARAMID AS PARAMID19_0_,
THIS_.PARAMNAME AS PARAMNAME19_0_, THIS_.PARAMVALUE AS PARAMVALUE19_0_
FROM PARAMS THIS_ WHERE THIS_.GROUPID = :1 AND THIS_.PARAMID = :2
\
WorkHour,TotalHours,
/
Returns 1 row:
GroupId = "WorkHour"
ParamId = "TotalHours"
ParamName = (empty)
ParamValue = YES
That was my fault. The ParamName value is not empty string. It's null! So, this is strange behaviour of NHibernate but reason is found.

Categories

Resources