I'm having trouble when I pull an object from the database with getting the framework to also get an an array in the object as well. I found that for sub objects the .Include("subobject") seems to work, but I can't get it to work for arrays or lists.
My Model:
public class RunData
{
[Key]
[Required]
public int id { get; set; }
public List<RunElement> Runs { get; set; }
public string[] DataLabels { get; set; }
}
List of Entities:
public class ProgramEntities:DbContext
{
public DbSet<RunData> RunData { get; set; }
public DbSet<RunElement> RunElement { get; set; }
}
Controller Code:
public ViewResult Details(int id)
{
RunData rundata = (from RunData in db.RunData.Include("Runs").in where RunData.id == id select RunData).First();
return View(rundata);
}
I did have all kinds of trouble with it not returning the list of Runs objects, but when I did the .Include("Runs") that fixed the problem. So, now my trouble is the DataLabels string array. If I try .Include("DataLabels") the program fails and says:
A specified Include path is not valid. The EntityType
'Program_Dataviewer.Models.RunData' does not declare a navigation
property with the name 'DataLabels'.
I have searched online some, I'm not seeing any clear cut answers. Thank you for the help.
You can not have collections of primitives in your data model, since each collection must be mapped to a table in the relational space (think about it - how is the database going to organize/save your collection?). What you can do is introduce a table / entity for DataLabels, e.g. something like this:
public class RunData
{
[Key]
[Required]
public int id { get; set; }
public List<RunElement> Runs { get; set; }
public List<DataLabel> DataLabels { get; set; }
}
public class DataLabel
{
[Key]
public int id { get; set; }
public string LabelName { get; set; }
}
Related
I have a report that I need to send to my react frontend that needs to be easily queried and searched. The problem is with the current method we need to pull all the entire database before performing a query due to nested objects and other factors.
To significantly speed up the process I want to create a Report Table/View to query from that stays up to date as the other tables change.
Here is a small example of the models:
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int TypeId { get; set; }
public ItemType Type { get; set; }
public int OrderId { get; set; }
public Order Order { get; set; }
}
public class ItemType
{
public int Id { get; set; }
public string Name { get; set; }
public List<Item> Items { get; set; }
}
public class Order
{
public int Id { get; set; }
public Customer Customer { get; set; }
public List<Item> Items { get; set; }
}
public class ItemReport
{
public string Name { get; set; }
public string Description { get; set; }
public string Type { get; set; }
public string Customer { get; set; }
public ItemReport(Item item)
{
Name = item.Name;
Description = item.Description;
Type = item.Type.Name;
Customer = item.Order.Customer.Name;
}
}
ItemReport is the model I use to send to the frontend.
I've read a fair amount on Keyless Entities and Views, but am in need of a little guidance on putting all the pieces together.
Currently we would pull all Items and required fields like Name from customer and turn it into an IEnumerable list of ItemReport to then be filtered/sorted or searched.
As a side note, there may be other solutions than the on I'm posting for that I would be open to as well.
I've looked into this quite a bit, but I don't think I'm finding the right solutions as an example when reading on Views they mention how they cannot be inserted into or updated with EF Core.
It is simple projection. Passing item in Constructor is not right way, because EF Core cannot look into compiled method body.
var query = context.Items
.Select(item => new ItemReport
{
Name = item.Name;
Description = item.Description;
Type = item.Type.Name;
Customer = item.Order.Customer.Name;
});
My database has two tables - RuleGroups and Rules. My Entity Framework classes are the following:
public class RuleGroup
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<Rule> Rules { get; set; }
}
public class Rule
{
[Key]
public Guid Id { get; set; }
public Guid RuleGroupId { get; set; }
public string Name { get; set; }
public ICollection<Condition> Conditions { get; set; }
[ForeignKey("RuleGroupId")]
public virtual RuleGroup RuleGroup { get; set; }
}
[NotMapped]
public class Condition
{
public Guid Id { get; set; }
public string Name { get; set; }
}
Class Condition is not mapped because it is being serialized and stored as JSON in Rule Table (using this example)
My DTOS are the following:
public class UpdateRuleGroupDto
{
public string Name { get; set; }
public ICollection<UpdateRuleDto> Rules { get; set; }
}
public class UpdateRuleDto
{
public string Name { get; set; }
public ICollection<UpdateConditionDto> Conditions { get; set; }
}
public class UpdateConditionDto
{
public string Name { get; set; }
}
In my Startup.cs I initialize Automapper :
AutoMapper.Mapper.Initialize(cfg =>
{
cfg.CreateMap<UpdateRuleGroupDto, RuleGroup>();
cfg.CreateMap<UpdateRuleDto, Rule>();
cfg.CreateMap<UpdateConditionDto, Condition>();
}
I have an API controller endpoint that accepts JSON PATCH document to make changes to data stored in database.
public IActionResult Patch(Guid ruleGroupId, [FromBody]JsonPatchDocument<UpdateRuleGroupDto> body)
{
RuleGroup ruleGroupFromRepo = _deviceRules.GetRuleGroup(ruleGroupId);
UpdateRuleGroupDto ruleGroupToPatch = Mapper.Map<UpdateRuleGroupDto>(ruleGroupFromRepo);
// Patching logic here
Mapper.Map(ruleGroupToPatch, ruleGroupFromRepo);
context.SaveChanges();
return NoContent();
}
The problem:
When changes are made/saved, Rules in Rule table change their/get new GUID.
Example, say we have this data in 2 Tables.
RuleGroup Table
[Id][Name]
[ddad5cac-e5a1-4db7-8167-66a6de3b8a0c][Test]
Rule Table
[Id][RuleGroupId][Name][Condition]
[17c38ee8-4158-4ecc-b893-97786fa76e13][ddad5cac-e5a1-4db7-8167-66a6de3b8a0c][Test][[{"Name":"Test"}]]
If I change field [Name] to a new value, Rules Table will look like this.
Rule Table
[Id][RuleGroupId][Name][Condition]
[ba106de8-bcbc-4170-ba56-80fe619cd757][ddad5cac-e5a1-4db7-8167-66a6de3b8a0c][Test2][[{"Name":"Test"}]]
Note that [Id] field has now a new GUID.
EDIT
#Gert Arnold made me realize that I'm not attaching entities.
I ran the following code:
foreach (var item in ruleGroupFromRepo.rules)
{
var x = _context.Entry(item).State;
}
and all the states were Added and not modified. Now I just have to figure out how to do it properly.
I have a sqlite database which has some tables and columns like the following:
int Id
text Name
text Comment
...
And my object in my project looks like this:
Public Class Entry {
public int Id { get; set; }
public String Name { get; set; }
public String Comment { get; set; }
public String Additional { get; set; }
}
This can happen, because my programm need to handle different versions of the database.
EF Core now trys to access the Additional field of the database but returns an error that it cannot find the field. (Expected behaviour)
Now my question is, if there is a way to ignore this error and return a default value for the property?
I could bypass the error by making the properties nullable. But i don't want to check each property with .HasValue() before accessing it. Because the real database has 50+ columns in the table.
https://www.entityframeworktutorial.net/code-first/notmapped-dataannotations-attribute-in-code-first.aspx
Put NotMapped as an attribute on the Additional field:
using System.ComponentModel.DataAnnotations.Schema;
Public Class Entry {
public int Id { get; set; }
public String Name { get; set; }
public String Comment { get; set; }
[NotMapped]
public String Additional { get; set; }
}
This tells EF that the field is not a column in the database.
I would advise you to split your domain object from that persisted dto object. That way you can have different dtos with different mappings. Now you can instantiate your domain object with your dto and decide inside your domain object what values are the correct default values.
public class Entry
{
public int Id { get; set; }
public string Name { get; set; }
public string Comment { get; set; }
public string Additional { get; set; }
}
public class EntryDtoV1
{
public int Id { get; set; }
public string Name { get; set; }
public string Comment { get; set; }
}
public class EntryDtoV2
{
public int Id { get; set; }
public string Name { get; set; }
public string Comment { get; set; }
public string Additional { get; set; }
}
Now you only need to create some kind of factory that creates the correct repository depending on what database version you query.
I have following RecipeModel, IngredientModel and RecipePartModel classes which represent the DTO classes for the frontend user:
public class RecipeModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public string ImageUrl { get; set; }
public string Description { get; set; }
public IEnumerable<RecipePartModel> RecipeParts { get; set; }
}
public class IngredientModel
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class RecipePartModel
{
public Guid Id { get; set; }
public IngredientModel Ingredient { get; set; }
public string Unit { get; set; }
public decimal Quantity { get; set; }
}
Here are my entity classes:
public class Recipe : BaseEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
public string ImageUrl { get; set; }
public string Description { get; set; }
public virtual IEnumerable<RecipePart> RecipeParts { get; set; }
}
public class Ingredient : BaseEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
public int Amount { get; set; }
public virtual IEnumerable<RecipePart> RecipeParts { get; set; }
}
public class RecipePart : BaseEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid Id { get; set; }
public Ingredient Ingredient { get; set; }
public Recipe Recipe { get; set; }
public string Unit { get; set; }
public decimal Quantity { get; set; }
}
My question is - how can I map the Recipe to RecipeModel using AutoMapper? I tried something like this but I assume it is bad, because it just join all the RecipeParts for the whole database, am I correct?
public class DomainProfile : Profile
{
public DomainProfile()
{
CreateMap<Ingredient, IngredientModel>().ReverseMap();
CreateMap<Recipe, RecipeModel>()
.ForMember(x => x.RecipeParts, opt => opt.MapFrom(src => src.RecipeParts));
}
}
To answer your question about how to use AutoMapper to map a type to another type, there are many ways of doing this. Documentation is here: http://docs.automapper.org/en/stable/Getting-started.html.
I wrote a console app and got it working in the quickest way I know possible using your code. When I debug this, and check inside recipeModel, it references a list of RecipePartModels with a single RecipePartModel. Inside that RecipePartModel, it references an IngredientModel.
static void Main(string[] args)
{
var profile = new DomainProfile();
Mapper.Initialize(cfg => cfg.AddProfile(profile));
var recipe = new Recipe
{
RecipeParts = new List<RecipePart>
{
new RecipePart()
{
Ingredient = new Ingredient()
}
}
};
var recipeModel = Mapper.Map<Recipe, RecipeModel>(recipe);
Console.ReadKey();
}
To answer your concern about getting all recipes from the database, if you're using Entity Framework, it depends on if you have lazy loading turned on. Lazy loading ensures that, when you get a recipe from the database, the recipe parts will not be loaded. They will only be loaded when you access the recipe part directly later on in the program flow. Lazy loading is turned on by default so this is the default behaviour. If you turn it off, you've enabled eager loading which loads all recipe parts and in turn their ingredient.
This might help: http://www.entityframeworktutorial.net/lazyloading-in-entity-framework.aspx.
There is nothing bad about this mapping. In fact you don't even need the ForMember call as this is the default convention. The mapping will simply convert each element in the entity child collection to a corresponding model object.
Of course, whether you load your entities in an efficient manner is another matter. If you load a large amount of Recipe entities, and lazy load the RecipeParts collections for each, you will have a major "SELECT N+1" problem. But this is not the fault of AutoMapper.
Let's say I have these models:
public class Component
{
public int Id { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public List<ComponentUpdate> Updates { get; set; }
public ComponentUpdate LastUpdate { get; set; }
}
public class ComponentUpdate
{
public int Id { get; set; }
public DateTime Timestamp { get; set; }
public Component Component { get; set; }
public string Message { get; set; }
}
The reason I'm saving the LastUpdate field instead of manually pulling it according to the highest 'TimeStamp' is because of speed. It would be faster to store a reference instead of checking the entire list every request.
When I'm trying to migrate the DB it throws an error saying I cannot have my properties participate in more than a single relationship.
I'm mapping the relationships in my context class and I don't think I'm doing it right since I have ComponentUpdate.Component mapped twice.
I've looked on several solutions but some were outdated and some just did not fit this scenario.
Thanks for helping.
Edit
Mapping accordingly:
modelBuilder.Entity<Component>().HasMany(c => c.Updates).WithOne(u => u.Component);
modelBuilder.Entity<ComponentUpdate>().HasOne(u => u.Component).WithOne(c => c.LastUpdate);