Mapping enum properly in EF Core Code First Approach - c#

I have a library application and am trying to go with a code first approach which has a Book entity like this:
public class Book
{
public int Id { get; set; }
public string Code { get; set; }
public string Title { get; set; }
public string Publisher { get; set; }
public Category? Category { get; set; }
}
public enum Category : byte
{
[Description("LA_Category_Thriller")]
Thriller,
[Description("LA_Category_Adventure")]
Adventure,
[Description("LA_Category_Comic")]
Comic,
[Description("LA_Category_Mistery")]
Mistery
}
Now I am coding a BookConfiguration class and I am not sure if I am doing stuff right; I am not sure what is the best solution, to convert to string, or to also enter it in DB like SQL-enum (not sure if even possible in EF Core):
public class BookConfiguration : IEntityTypeConfiguration<Book>
{
public void Configure(EntityTypeBuilder<Book> builder)
{
builder.ToTable(nameof(Book), "lib");
builder.HasKey(b => b.Id);
builder.Property(b => b.Id).ValueGeneratedOnAdd();
builder.Property(b => b.Category)
.HasMaxLength(32)
.HasConversion<string>();
}
}
As far as I got stuff this is goint to convert it to string upon updating an entry, but will this make stuff harder later when I retrieve values from db?

Related

How to work only with the Entity Framework Scaffolded Models from Database

I have been working with .Net Core Entity Framework database first approach with the Scaffolding technique.
It generated me a couple Models/Classes from my Database Tables, but for now, I will just minimize the issue I am having to this two tables... a relation one to many on the both ChampionID column:
So, after scaffolding/mapping the models with EntityCore Tools it generated the following two classes (and several others that are not relevant):
Champion.cs:
public partial class Champion
{
public Champion()
{
ChampionScreenshot = new HashSet<ChampionScreenshot>();
ChampionUser = new HashSet<ChampionUser>();
ChampionUserRate = new HashSet<ChampionUserRate>();
}
public int ChampionId { get; set; }
public string Name { get; set; }
public string Nickname { get; set; }
public string Description { get; set; }
public string ImagePath { get; set; }
public byte AttackDamageScore { get; set; }
public byte AbilityPowerScore { get; set; }
public byte ResistanceScore { get; set; }
public byte PlayingDifficult { get; set; }
public int PrimaryClassId { get; set; }
public int SecondaryClassId { get; set; }
public ChampionClass PrimaryClass { get; set; }
public ChampionClass SecondaryClass { get; set; }
public ICollection<ChampionScreenshot> ChampionScreenshot { get; set; }
public ICollection<ChampionUser> ChampionUser { get; set; }
public ICollection<ChampionUserRate> ChampionUserRate { get; set; }
}
ChampionScreenshot.cs:
public partial class ChampionScreenshot
{
public int ChampionScreenshotId { get; set; }
public string ImagePath { get; set; }
public int ChampionId { get; set; }
public Champion Champion { get; set; }
}
My doubt is: what is the correct way to retrieve a Champion object with the ChampionScreenshot attribute filled?
For example, this is what I am doing in my Service layer:
public async Task<Champion> GetChampion(int id)
{
Champion champion = await _context.Champion.FirstAsync(m => m.ChampionId == id);
champion.ChampionScreenshot = _context.ChampionScreenshot.ToListAsync().Result.FindAll(m => m.ChampionId == champion.ChampionId);
return champion;
}
So I am basically getting a specific Champion and then filling the ChampionScreenshot attribute (which is also a Class) separately, but the thing is that inside my ChampionScreenshot there is also a Champion class attribute which fully loads once again:
Which is obviously generating an error once it is exposed in the endpoint of the Restful Service:
[Produces("application/json")]
[Route("api/Champions")]
public class ChampionsController : Controller
{
[HttpGet("{id}")]
public async Task<IActionResult> GetChampion([FromRoute] int id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var champion = await _service.GetChampion(id);
if (champion == null)
{
return NotFound();
}
return Ok(champion);
}
...
Error:
Newtonsoft.Json.JsonSerializationException: Self referencing loop detected for property 'champion' with type 'ChampionsService.Models.Champion'. Path 'championScreenshot[0]'.
So, I was thinking in just creating my custom model and fill it with the data extracted from my DbContext instead of returning the models already created but I really think that there should be a way to fully use only the mapped Models, I was wondering that...
Champion references itself:
Champion > multiple ChampionScreenshot > Champion (back to the original object)
That's easy to solve:
return Json(champion, new JsonSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore });
Or you could do it for the entire application:
services.AddMvc().AddJsonOptions(opts =>
{
opts.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
And then just:
return Json(champion);
The following troubles me, though:
Champion champion = await _context.Champion.FirstAsync(m => m.ChampionId == id);
champion.ChampionScreenshot = _context.ChampionScreenshot.ToListAsync().Result.FindAll(m => m.ChampionId == champion.ChampionId);
You are saying "go to the database, download every single championscreenshot and find the ones I want through an in-memory search". That's not only horrible slow, it also wastes a lot of resources in your application and in the database. For including data, you use Include:
Champion champion = await _context.Champion
.Include(x => x.ChampionScreenshot)
.FirstAsync(x => x.ChampionId == id);
(this says "go to the database and bring me the champion but also include all the ChampionScreenshot that correspond, through an inner join).

C# MVC - combine 2 models into 1 model

I am currently learning MVC in C#, and faced with below problems.
I have 2 models created already, and both work fine.
Model 1:
public class MeetingResolutionViewModel
{
public string ResolutionNo { get; set; }
public string ResolutionCd { get; set; }
public string ResolutionDesc { get; set; }
public long EventId{ get; set; }
}
Model 1 is mapped like this:
public static void MapToMeetingResolution(IMapperConfigurationExpression cfg)
{
cfg.CreateMap<PxVotingResolution, MeetingResolutionViewModel>()
.ForMember(dest => dest.ResolutionId, m => m.MapFrom(src => src.Id))
.ReverseMap();
cfg.CreateMap<PxVotingElection, MeetingResolutionViewModel>()
.ReverseMap();
}
Model 2:
public class ProxyVotingViewModel
{
public bool RoleUpdate { get; set; }
public bool RoleElect { get; set; }
public long EventId { get; set; }
public DateTime? LastUpdatedAt { get; set; }
}
Model 2 is mapped like this:
public static void MapToProxyMeeting(IMapperConfigurationExpression cfg)
{
cfg.CreateMap<EventDetail, ProxyVotingViewModel>();
}
Model 1 and Model 2 share the same EventId column.
I am creating another view model like below. It is a combination of above 2 models with the "MeetingResolutionViewModel" model in a list.
public class ProxyVotingEmailViewModel
{
public List<MeetingResolutionViewModel> MeetingResolutionViewModel { get; set; }
public ProxyVotingViewModel ProxyVotingViewModel { get; set; }
}
Using auto mapper, I have done the mapping as following:
public static void MapToProxyVotingEmailViewModel(IMapperConfigurationExpression cfg)
{
cfg.CreateMap<ProxyVotingViewModel, ProxyVotingEmailViewModel>();
cfg.CreateMap<MeetingResolutionViewModel, ProxyVotingEmailViewModel>();
}
In my function below, how can I solve the 2 problems mentioned in the comment?
public async Task<CommandResult<List<string>>> SendEmail(List<MeetingResolutionViewModel> data)
{
//blah blah
var emailData = new ProxyVotingEmailViewModel();
var pxData = new ProxyVotingViewModel();
/* I would like to do 2 things:
* 1) get pxData using the EventId from data[0];
* 2) get emailData value by combining pxData with data (as per the view model definition)
**/
//blah blah
}

Error Creating Controller With Custom Type using Entity Framework MVC ASP.NET

I'm trying to create a controller with a model that has custom types in it...
I have a main class with ID and a type 'Work', where I declared three properties, one of which relies on the other two. Then I created a dbset type. Are my mapping properties incorrect?
I am getting the following error:
There was an error running the selected code generator: "Unable to retrieve metadata for 'Stack.Models.Work'". the property sum is not a declared property on type Math. Verify that the property has not been explicitly excluded from the model by using the Ignore Method or NotMappedAttribute data annotation. Make sure it is a valid primitive property.
namespace stack.Models
{
public class Work
{
[Key]
public int ID { get; set; }
public Work ()
{
this.Maths = new Math();
}
public Math Maths { get; set; }
}
[ComplexType]
public class Math
{
public int first { get; set; }
public int second { get; set; }
public int sum
{
get
{
try
{
return first + second;
}
catch
{
return 0;
}
}
}
}
public class WorkDBContext: DbContext
{
public DbSet<Work> Working { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Work>()
.Property(c => c.Maths.first).IsRequired();
modelBuilder.Entity<Work>()
.Property(c => c.Maths.second).IsRequired();
modelBuilder.Entity<Work>()
.Property(c => c.Maths.sum).IsRequired();
base.OnModelCreating(modelBuilder);
}
}
}
Sum is not a simple type (column in the database) it is a data function(calculated from other properites), you do not have to store it in the database.
[ComplexType]
public class Math
{
public int first { get; set; }
public int second { get; set; }
[NotMapped]
public int sum
{
get
{
return first + second;
}
}
}
Remove this line:
modelBuilder.Entity<Work>()
.Property(c => c.Maths.sum).IsRequired();

How to work with field of inherited classes in Entity Framework

Suppose, i have main class for data representation and this class have configuration field. This field must be able to answer some questions related to main class (assume, that this is one question - 'IsMainClassReadyToUse'). But inner structure of this class may be different.
Because of it, i want create abstract class Configurator and depending on situation use various Configuratos that implement its functional.
So, i have following code:
public class SimpleConfigurator : Configurator
{
public int FieldA { get; set; }
public override bool IsDataClassReadyToUse()
{
return ParentDataClass.FieldA == FieldA;
}
}
public class ComplexConfigurator : Configurator
{
public virtual List<int> FieldsB { get; set; }
public override bool IsDataClassReadyToUse()
{
return ParentDataClass.FieldsB.All(x => FieldsB.Any(y => y == x));
}
}
public abstract class Configurator
{
public int ConfiguratorId { get; set; }
public virtual DataClass ParentDataClass { get; set; }
public abstract bool IsDataClassReadyToUse();
}
public class DataClass
{
public int DataClassId { get; set; }
public virtual Configurator Configurator { get; set; }
public int FieldA { get; set; }
public virtual List<int> FieldsB { get; set; }
}
public class DataDbContext : DbContext
{
public DbSet<DataClass> DataClasses { get; set; }
}
But the problem appears when i try use DataClass instance with Configurator of type ComplexConfigurator.
Because of LazyLoading i need to load FieldsB from ComplexConfigurator, but abstract class Configurator doesn't contain such field and i can't write such code:
new DataDbContext().DataClasses
.Include(m => m.Configurator)
.Include(m => m.Configurator.FieldsB);
I tried to disable LazyLoading, adding such constructor in DataDbContext:
public DataDbContext()
{
Configuration.LazyLoadingEnabled = false;
}
But when i try get access to FieldsB it still be null.
So, how can i implement such architecture with Entity Framework?
Or maybe i should choose another architecture for such task?
I think you should try access you configurator such as
((ComplexConfigurator)yourObject.Configurator).FieldsB
But I'm afraid EF works wrong with List<int> property (when I tried do that sometimes I've got a fail) and better way is to create class Option and field List<Option> Options into your configurator instead of List with integers.
You also should check your DB scheme (there's should be a table "Configurators" with idenitifator field and all SimpleConfigurator and ComplexConfigurator's fields). May be you should add DbSet<Configurator> into your DbContext definition.
You can read this article for getting more information about inheritance and EF.

Mapping a simple array

I'm using fluent Nhibernate to map a simple class
And using Schema Generation to create this class on MySQL DB.
I can't use IList<> for my properties (I'm mapping cross-language domain classes)
So I have to use simple arrays..
I Want NHibernate to create a connection table between the two classes,
These are the domain classes:
public class ClassOne
{
public virtual Guid Guid { get; set; }
public virtual String Title { get; set; }
public virtual ClassTwo[] Tags { get; set; }
}
public class ClassTwo
{
public virtual Guid Guid { get; set; }
public virtual string Title { get; set; }
}
And this is the map:
public class ClassOneMap : ClassMap<ClassOneMap>
{
public ClassOneMap ()
{
Id(x => x.Guid).GeneratedBy.GuidComb();
Map(x => x.Title);
HasManyToMany(x => x.Tags)
.Cascade.SaveUpdate());
}
}
public class ClassTwoMap : ClassMap<ClassTwo>
{
public ClassTwoMap()
{
Id(x => x.Guid).GeneratedBy.GuidComb();
Map(x => x.Title);
}
}
The schema generates great! It has a ClassOne, ClassTwo and ClassTwoToClassOne Tables
But when I'm trying to persist an instance of ClassOne I have an Invalid Cast exception..
This is solved by changing the arrays to IList's but I can't really do that..
Can anyone tell me how to configure the Fluent mapping to use Arrays without changing the schema architecture?
Thanks A'lot!
Ok, played around this and hope that solve the question.
So models are:
public class ClassOne : Entity
{
public virtual string Title { get; set; }
public virtual ClassTwo[] Tags { get; set; }
}
public class ClassTwo : Entity
{
public virtual string Title { get; set; }
}
Base class contains the Id definition which is long in my case. Should not be a problem with Guids
Mapping class: We are using FluentNhibernate with some convention, also the idea is in HasManyToMany
public class ClassOneMappingOverride : IAutoMappingOverride<ClassOne>
{
public void Override(AutoMapping<ClassOne> mapping)
{
mapping.HasManyToMany(x => x.Tags).AsArray(x => x.Id).ParentKeyColumn("classOneId")
.ChildKeyColumn("classTwoId")
.Table("ClassOneLinkClassTwo")
.Cascade.SaveUpdate();
}
}
Please note that if you not indicate ParentKey, ChildKey and Table it will not create the link table.
The unit test which insert data looks like:
public class ClassOneDataPart : DataPartBase, IDataPart
{
public void AddToDatabase()
{
var classOne = new ClassOne { Title = "classOne" };
var classTwo1 = new ClassTwo { Title = "class21" };
var classTwo2 = new ClassTwo { Title = "class22" };
var tags = new[] { classTwo1, classTwo2 };
classOne.Tags = tags;
this.SaveData(classOne);
this.SaveData(classTwo1);
this.SaveData(classTwo2);
}
}
and the result into database is:
Regards,
Ion
Map the collection as a private field and expose it as an array. This also makes it easy to expose AddTag and RemoveTag methods without manipulating the array.
public class ClassOne
{
private IList<ClassTwo> _tags;
public virtual Guid Guid { get; set; }
public virtual String Title { get; set; }
public virtual ClassTwo[] Tags
{
// possibly expose as method to hint that the array is re-built on every call
get { return _tags.ToArray(); }
}
}
public class ClassOneMap : ClassMap<ClassOne>
{
public ClassOneMap ()
{
Id(x => x.Guid).GeneratedBy.GuidComb();
Map(x => x.Title);
HasManyToMany(x => x.Tags).Access.CamelCaseField(Prefix.Underscore)
.Cascade.SaveUpdate());
}
}
Try to use .AsArray(x=>x.Id)

Categories

Resources