ASP.NET Core : Fluent Api relationships configuration - asp.net-core

There are a lot of examples how to use Fluent API in the internet, but mostly shows how configure one relationship between two models. In my case I need 3 relationships between 2 models. How to configure relationships between models below with Fluent API?
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public int FinanceEstimateId { get; set; }
public Estimate FinanceEstimate { get; set; }
public int EnvironmentEstimateId { get; set; }
public Estimate EnvironmentEstimate { get; set; }
public int SelfEstimateId { get; set; }
public Estimate SelfEstimate { get; set; }
}
public class Estimate
{
public int Id { get; set; }
public string Name { get; set; } // like: bad, good, excellent
public float Value { get; set; } // like: 1,2,3
}

Maybe this points you in the right direction.
I would go for 2 configurations like:
public class CompanyConfiguration : IEntityTypeConfiguration<Company>
{
public void Configure(EntityTypeBuilder<Company> builder)
{
builder.ToTable("Companies");
builder
.HasOne(x => x.EnvironmentEstimate)
.WithMany()
.HasForeignKey(x => x.EnvironmentEstimateId)
.OnDelete(DeleteBehavior.NoAction);
builder
.HasOne(x => x.FinanceEstimate)
.WithMany()
.HasForeignKey(x => x.FinanceEstimateId)
.OnDelete(DeleteBehavior.NoAction);
builder
.HasOne(x => x.SelfEstimate)
.WithMany()
.HasForeignKey(x => x.SelfEstimateId)
.OnDelete(DeleteBehavior.NoAction);
}
}
public class EstimateConfiguration : IEntityTypeConfiguration<Estimate>
{
public void Configure(EntityTypeBuilder<Estimate> builder)
{
builder.ToTable("Estimates");
}
}
You need a DbContext:
public class MyDbContext : DbContext
{
public DbSet<Company> Companies { get; set; } = null!;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"CONNECTIONSTRING");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// applies the configuration (those IEntityTypeConfiguration<T> things)
modelBuilder.ApplyConfigurationsFromAssembly(typeof(MyDbContext).Assembly);
}
}
I created a console application that demonstrates the usage
using var ctx = new MyDbContext();
await ctx.Database.EnsureDeletedAsync();
await ctx.Database.EnsureCreatedAsync();
var company1 = new Company
{
Name = "Name1",
EnvironmentEstimate = new Estimate { Name = "EnvironmentEstimate1", Value = 1 },
FinanceEstimate = new Estimate { Name = "FinanceEstimate1", Value = 2 },
SelfEstimate = new Estimate { Name = "SelfEstimate1", Value = 3 }
};
var company2 = new Company
{
Name = "Name2",
EnvironmentEstimate = new Estimate { Name = "EnvironmentEstimate2", Value = 4 },
FinanceEstimate = new Estimate { Name = "FinanceEstimate2", Value = 5 },
SelfEstimate = new Estimate { Name = "SelfEstimate2", Value = 6 }
};
await ctx.Companies.AddAsync(company1);
await ctx.Companies.AddAsync(company2);
await ctx.SaveChangesAsync();
var result = await ctx.Companies.ToListAsync();
Console.WriteLine("Done");

Related

system.outofmemoryexception swashbuckle.aspnetcore

I am having this issue when I am dealing with Geometry datatypes when I change the property to string everything works like a charm. Below you may see that I used schema filter to remove Ignored data member , and document filter to remove anything related to nettopology.
Property Name = GeoPoly
Swagger Config Class
public static IServiceCollection AddSwaggerModule(this IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v2", new OpenApiInfo { Title = "Test API", Version = "0.0.1" });
c.SchemaFilter<MySwaggerSchemaFilter>();
c.DocumentFilter<RemoveBogusDefinitionsDocumentFilter>();
c.ResolveConflictingActions(x => x.First());
});
return services;
}
public static IApplicationBuilder UseApplicationSwagger(this IApplicationBuilder app)
{
app.UseSwagger(c =>
{
c.RouteTemplate = "{documentName}/api-docs";
});
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/v2/api-docs", "Test API");
});
return app;
}
}
public class MySwaggerSchemaFilter : Swashbuckle.AspNetCore.SwaggerGen.ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (schema?.Properties == null)
{
return;
}
var ignoreDataMemberProperties = context.Type.GetProperties()
.Where(t => t.GetCustomAttribute<IgnoreDataMemberAttribute>() != null);
foreach (var ignoreDataMemberProperty in ignoreDataMemberProperties)
{
var propertyToHide = schema.Properties.Keys
.SingleOrDefault(x => x.ToLower() == ignoreDataMemberProperty.Name.ToLower());
if (propertyToHide != null)
{
schema.Properties.Remove(propertyToHide);
}
}
}
}
public class RemoveBogusDefinitionsDocumentFilter : Swashbuckle.AspNetCore.SwaggerGen.IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
swaggerDoc.Components.Schemas.Remove("Districts");
swaggerDoc.Components.Schemas.Remove("Geometry");
swaggerDoc.Components.Schemas.Remove("CoordinateSequenceFactory");
swaggerDoc.Components.Schemas.Remove("GeometryOverlay");
swaggerDoc.Components.Schemas.Remove("NtsGeometryServices");
swaggerDoc.Components.Schemas.Remove("CoordinateEqualityComparer");
swaggerDoc.Components.Schemas.Remove("NtsGeometryServices");
swaggerDoc.Components.Schemas.Remove("GeometryFactory");
swaggerDoc.Components.Schemas.Remove("OgcGeometryType");
swaggerDoc.Components.Schemas.Remove("Coordinate");
swaggerDoc.Components.Schemas.Remove("Point");
}
}
Entity Class
public class Districts : BaseEntity<long>
{
public string DistrictsDesc { get; set; }
public string DistrictsDescAr { get; set; }
[IgnoreDataMember]
[Column(TypeName = "geometry")]
public Geometry GeoPoly { get; set; }
public IList<Records> Records { get; set; } = new List<Records>();
public long? RegionsId { get; set; }
public Regions Regions { get; set; }
public long? CitiesId { get; set; }
public Cities Cities { get; set; }
}
Is there a way to stop swashbuckle gen from dealing with datatypes other than documents filter ?

AutoMapper Custom Value Resolvers async

I want to convert BotViewModel to Bot using AutoMapper.
There is an example input below from type BotViewModel. If I want to add a new bot to the database, that model should be matched/mapped to Bot model and Bot's foreign keys should be resolved.
In other words, BotViewModel.Symbol should be matched to CryptoPair.Symbol and BotViewModel.Interval to TimeInterval.Interval.
{
"id": 0,
"name": "Bot 1",
"status": true,
"symbol": "LTCBTC",
"interval": 6
}
My code below is working exactly as I described it above. It uses http://docs.automapper.org/en/stable/Custom-value-resolvers.html. The thing is that I want to make _context.CryptoPairs.FirstOrDefault async FirstOrDefault => FirstOrDefaultAsync.
I want to do that because I feel like that's not the best practice. Basically, I'm looking for an advice if that's okay or not. By the way, if the symbol or interval cannot be matched because it simply doesn't exist in the database, var bot = _mapper.Map<Bot>(botViewModel); should just throw an exception which is later handled by my controller.
public class CryptoPairResolver : IValueResolver<BotViewModel, Bot, int>
{
private readonly BinanceContext _context;
public CryptoPairResolver(BinanceContext context)
{
_context = context;
}
public int Resolve(BotViewModel source, Bot destination, int destMember, ResolutionContext context)
{
return _context.CryptoPairs.FirstOrDefault(p => p.Symbol == source.Symbol).Id;
}
}
public class TimeIntervalResolver : IValueResolver<BotViewModel, Bot, int>
{
private readonly BinanceContext _context;
public TimeIntervalResolver(BinanceContext context)
{
_context = context;
}
public int Resolve(BotViewModel source, Bot destination, int destMember, ResolutionContext context)
{
return _context.TimeIntervals.FirstOrDefault(i => i.Interval == source.Interval).Id;
}
}
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<BotViewModel, Bot>()
.ForMember(dest => dest.CryptoPairId, opt => opt.MapFrom<CryptoPairResolver>())
.ForMember(dest => dest.TimeIntervalId, opt => opt.MapFrom<TimeIntervalResolver>());
}
}
Models:
public class Bot
{
public int Id { get; set; }
public string Name { get; set; }
public bool Status { get; set; }
public int CryptoPairId { get; set; }
public CryptoPair CryptoPair { get; set; }
public int TimeIntervalId { get; set; }
public TimeInterval TimeInterval { get; set; }
}
public class BotViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public bool Status { get; set; }
public string Symbol { get; set; }
public KlineInterval Interval { get; set; }
}
public class CryptoPair
{
public int Id { get; set; }
public string Symbol { get; set; }
public List<Bot> Bots { get; set; }
}
public class TimeInterval
{
public int Id { get; set; }
public KlineInterval Interval { get; set; }
public List<Bot> Bots { get; set; }
}
Edit:
public async Task<Bot> CreateAsync(BotViewModel botViewModel)
{
// Map BotViewModel to Bot
var cryptoPair = await _context.CryptoPairs.FirstOrDefaultAsync(e => e.Symbol == botViewModel.Symbol);
if (cryptoPair == null)
{
throw new BadRequestException();
}
var timeInterval = await _context.TimeIntervals.FirstOrDefaultAsync(e => e.Interval == botViewModel.Interval);
if (timeInterval == null)
{
throw new BadRequestException();
}
var bot = new Bot
{
Name = botViewModel.Name,
Status = botViewModel.Status,
CryptoPairId = cryptoPair.Id,
TimeIntervalId = timeInterval.Id
};
// Create code
_context.Bots.Add(bot);
await _context.SaveChangesAsync();
return bot;
}

Asp.net Core does not serialize my new type

Am using .net core +angular 5, and trying to return a list, but one field is null in JSON response. Am using Postman to trigger debugging and saw in VS that the field has a value coming from the DB.
Don't know why it doesn't in the JSON response.
[HttpGet("[action]")]
public IEnumerable<HikingTrail> HikingTrails()
{
var dbOptions = new DbContextOptionsBuilder<HikingTrailContext>();
dbOptions.UseSqlServer("Server = (localdb)\\mssqllocaldb; Database = HikingApp");
var dbContext = new DAO.HikingTrailContext(dbOptions.Options);
return dbContext.HikingTrails.ToList();
}
This returns:
I'm interested in the "mountainRange" field not being null. In debug window it has the right value.
{
"url": null,
"hikingTrailId": 159,
"mountainRange": null,
"name": "My custom name",
"startPoint": null,
"endPoint": null,
"trailCheckpoints": null,
"type": 2,
"dificulty": null,
"duration": "2 1/2 - 3 h",
"minDuration": "00:00:00",
"maxDuration": "00:00:00",
"seasonality": "mediu",
"equipmentLevel": null,
"trailMarking": null,
"hasTrailType": false
},
I was thinking maybe it's EF Core, and have made this 2nd try (i.e. added Include() to dbContext query):
[HttpGet("[action]")]
public IEnumerable<HikingTrail> HikingTrails()
{
var dbOptions = new DbContextOptionsBuilder<HikingTrailContext>();
dbOptions.UseSqlServer("Server = (localdb)\\mssqllocaldb; Database = HikingApp");
var dbContext = new DAO.HikingTrailContext(dbOptions.Options);
return dbContext.HikingTrails.Include( x => x.MountainRange).ToList();
}
Could not get any response in Postman.
EDIT:
public class HikingTrailContext : DbContext
{
public HikingTrailContext(DbContextOptions<HikingTrailContext> options) : base(options)
{
}
public HikingTrailContext():base(){
}
public DbSet<HikingTrail> HikingTrails { get; set; }
public DbSet<MountainRange> MountainRanges { get; set; }
public DbSet<TrailScrapingSessionInfo> TrailScrapingHistory { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
}
public class HikingTrail
{
[Key]
public int HikingTrailId { get; set; }
public HikingTrail() { }
public MountainRange MountainRange { get; set; }
public String Name { get; set; }
public Location StartPoint { get; set; }
public Location EndPoint { get; set; }
public List<Location> TrailCheckpoints { get; }
public TrailType Type => TrailType.Undetermined;
public String Dificulty { get; set; }
public String Duration { get; set; }
public TimeSpan MinDuration { get; set; }
public TimeSpan MaxDuration { get; set; }
public String Seasonality { get; set; }
public String EquipmentLevel { get; set; }
public String TrailMarking { get; set; }
public String URL;
public bool HasTrailType
{
get
{
return this.Type != TrailType.Undetermined;
}
}
public override bool Equals(object obj)
{
return (((HikingTrail)obj).Name == this.Name);
}
public override int GetHashCode()
{
int hash = 17;
// Suitable nullity checks etc, of course :)
hash = hash * 23 + Name.GetHashCode();
hash = hash * 23 + Type.GetHashCode();
hash = hash * 23 + StartPoint.GetHashCode();
return hash;
}
public override string ToString()
{
return Name.ToString();
}
}
EDIT :
I profiled the db on dbContext.HikingTrails.Include(x => x.MountainRange).Where(x => x.MountainRange != null).ToList(); and the generated query is OK, meaning it has a Name column for MountainRange as well.
P.S.: several fields are null, but those ones don't have data yet.
found one solution, Projection to an anonymous type. Also had to be careful not to have two fields with the same name of "Name"
[HttpGet("[action]")]
public dynamic HikingTrails3()
{
var dbOptions = new DbContextOptionsBuilder<HikingTrailContext>();
dbOptions.UseSqlServer("Server = (localdb)\\mssqllocaldb; Database = HikingApp");
var dbContext = new DAO.HikingTrailContext(dbOptions.Options);
var trails = dbContext.HikingTrails.Include(x => x.MountainRange).
Select( i =>new { Name= i.Name, MountainRangeName = i.MountainRange.Name, i.Duration,
i.Dificulty,i.EquipmentLevel, i.Seasonality, i.Type }).ToList();
return trails;
}

Take function doesn't work and cannot be sent to RavenDB for query

Query Code:
var query = session.IndexQuery<App_OrgSearch.IndexResult, App_OrgSearch>();
var organizationUnitResults = query.Statistics(out stats)
.Skip(0)
.Take(5)
.AsProjection<Org>().ToList();
public static IRavenQueryable<TResult> IndexQuery<TResult, TIndex>(this IDocumentSession session)
where TIndex : AbstractIndexCreationTask, new()
{
return session.Query<TResult, TIndex>();
}
App_OrgSearch is the index I defined as below:
public class App_OrgSearch : AbstractIndexCreationTask<Org, App_OrgSearch.IndexResult>
{
public class IndexResult
{
public string Id { get; set; }
public string BusinessName { get; set; }
public string ShortName { get; set; }
public IList<string> Names { get; set; }
public List<string> PhoneNumbers { get; set; }
public List<OrganizationUnitPhone> OrganizationUnitPhones { get; set; }
}
public App_OrganizationUnitSearch()
{
Map = docs => from doc in docs
select new
{
Id = doc.Id,
Names = new List<string>
{
doc.BusinessName,
doc.ShortName,
},
BusinessName = doc.BusinessName,
ShortName = doc.ShortName,
PhoneNumbers = doc.OrganizationUnitPhones.Where(x => x != null && x.Phone != null).Select(x => x.Phone.Number),
};
Indexes.Add(x => x.Names, FieldIndexing.Analyzed);
}
}
I have 27 records in database. I want to take 5, but after query, all 27 records are returned. Why does Take function not work?
Your sample code seems wrong.
var query = session.IndexQuery<App_OrgSearch.IndexResult, App_OrgSearch>();
var organizationUnitResults = organizationUnitsQuery.Statistics(out stats)
What is organizationUnitsQuery ? You have the query as query, but there is no IndexQuery method on the session

FNH Mapping Overrides on HasMany

Sorry for the wall of text please don't TLDR.
I have a very simple object model, basically it's
public class Colony
{
public virtual IList<Ant> Ants { get; set; }
}
public class Ant
{
public bool Dead { get; set }
public virtual IList<Egg> Eggs { get; set; }
}
public class Egg
{
public bool Dead { get; set }
public virtual int IncubationPeriod { get; set; }
}
You get the idea. So I have declared two mapping overrides.
public class ColonyMappingOverride : IAutoMappingOverride<Colony>
{
public void Override(AutoMapping<Colony> mapping)
{
mapping.HasMany(c => c.Ants).Where(x => !x.Dead);
}
}
public class AntMappingOverride : IAutoMappingOverride<Ant>
{
public void Override(AutoMapping<Ant> mapping)
{
mapping.HasMany(c => c.Eggs).Where(x => !x.Dead);
}
}
So when I grab data out of the DB I end up with inconsistent data.
For example:
Colony.Ants doesn't contain any dead ants (as expected), however Colony.Ants[0].Eggs contains all Eggs... dead or not.
If I call a Session.Refresh(Colony.Ants[0]) the dead eggs get removed.
Anyone know why lazy loading is ignoring the Where clause on the Ants mapping override?
it works for me using FNH 2.0.1.0 and NH 4.0.0.4000 and following fixed code
public class Colony : Entity
{
public Colony()
{
Ants = new List<Ant>();
}
public virtual IList<Ant> Ants { get; set; }
}
public class Ant : Entity
{
public Ant()
{
Eggs = new List<Egg>();
}
public virtual bool Dead { get; set; }
public virtual IList<Egg> Eggs { get; set; }
}
public class Egg : Entity
{
public virtual bool Dead { get; set; }
public virtual int IncubationPeriod { get; set; }
}
public class ColonyMappingOverride : IAutoMappingOverride<Colony>
{
public void Override(AutoMapping<Colony> mapping)
{
mapping.HasMany(c => c.Ants).Where(x => !x.Dead);
}
}
public class AntMappingOverride : IAutoMappingOverride<Ant>
{
public void Override(AutoMapping<Ant> mapping)
{
mapping.HasMany(c => c.Eggs).Where(x => !x.Dead);
}
}
code
var config = Fluently.Configure()
.Database(SQLiteConfiguration.Standard.InMemory().ShowSql().FormatSql())
.Mappings(m => m
.AutoMappings.Add(AutoMap.AssemblyOf<Ant>()
.Conventions.Add(DefaultCascade.All())
.Override<Colony>(new ColonyMappingOverride().Override)
.Override<Ant>(new AntMappingOverride().Override)
)
)
.BuildConfiguration();
using (var sf = config.BuildSessionFactory())
using (var session = sf.OpenSession())
{
new SchemaExport(config).Execute(true, true, false, session.Connection, null);
using (var tx = session.BeginTransaction())
{
session.Save(new Colony
{
Ants =
{
new Ant
{
Dead = true,
Eggs =
{
new Egg { Dead = true, IncubationPeriod = 1 },
new Egg { Dead = false, IncubationPeriod = 2 },
}
},
new Ant
{
Dead = false,
Eggs =
{
new Egg { Dead = true, IncubationPeriod = 1 },
new Egg { Dead = false, IncubationPeriod = 2 },
}
},
}
});
tx.Commit();
}
session.Clear();
var result = session.QueryOver<Colony>()
.Fetch(c => c.Ants).Eager
.SingleOrDefault();
Console.WriteLine(result.Ants[0].Eggs.All(e => !e.Dead));
}