My goal is to create the poco classes without knowing which ORM is going to be used.
Now observe the following code...
public class NeuralModel
{
public NeuralModel()
{
Configurations = new HashSet<NeuralModelConfiguration>();
}
public int Id { get; set; }
public string Name { get; set; }
public DateTime? LastTrained { get; set; }
//Navigation properties
public ICollection<NeuralModelConfiguration> Configurations { get; set; }
//NotMapped properties
[NotMapped]
public NeuralModelConfiguration DefaultConfiguration { get { return Configurations.SingleOrDefault(config => config.IsDefault); } }
[NotMapped]
public bool IsTrained { get { return LastTrained.HasValue; } }
}
public class NeuralModelConfiguration
{
public NeuralModelConfiguration()
{
KeyValues = new HashSet<KeyValuePair<string, string>>();
}
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool IsDefault { get; set; }
public ICollection<KeyValuePair<string, string>> KeyValues
public int ModelId { get; set; }
//Navigation properties
public NeuralModel Model { get; set; }
}
Now suppose if I were to use Entity Framework Core, I get into the issue of mapping complex types (in my example it would be ICollection<KeyValuePair<string, string>>).
According to my research I came across two possible solutions:
Serialization
Another Entity
Now my question is that is there a third solution that do not require me to change my poco classes but rather do some sort of hidden processing at the dbcontext level?
Should there not be a third solution then out of the 2 available solutions which one would be considered better in terms of performance?
Related
I have below model structure
public class A621
{
public Guid? AirflowSource { get; set; }
public string AirflowSourceName { get; set; }
public string Category { get; set; }
}
public class A170
{
public Guid? AirflowSource { get; set; }
public string AirflowSourceName { get; set; }
}
I am using above models in entities like as below
public class MechanicalData
{
public List<A621> A621 { get; private set; }
public List<A170> A170 { get; private set; }
public List<Lab> Lab { get; private set; }
}
public class MechanicalTypeData
{
public MechanicalData MechanicalData { get; set; }
}
public class SpaceType : IAEIMaster, IRevisionData
{
[Column(TypeName = "jsonb")]
public MechanicalTypeData MechanicalTypeData { get; set; }
public string Note { get; set; }
public bool IsApproved { get; set; }
}
you can see that i am using this entity MechanicalTypeData in spacetype entity as json column and these entities A621 and A170 are child entities of MechanicalData ..
Now i would like to use A621 and A170 as a individual lookup tables and these are going to be really existing in DB. I am using EF core code first approach while creating tables.
my question is can i use A621 and A170 for both purposes, by both I mean as individual lookup tables and adding child objects to json that is going to be sit in column as json object.
Could any one throw some light on this or any other better approach as well.
many thanks in advance
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 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.
I am facing an issue with the Fluent API mapping in EF6. It is all set up, but for some reason, anytime I select an object, it is missing the child objects.
Lets start with the DbContext:
public partial class PMSContext : DbContext
{
public PMSContext() : base(nameOrConnectionString: "PmsDb")
{
this.Configuration.ProxyCreationEnabled = false;
this.Configuration.LazyLoadingEnabled = false;
}
public DbSet<Employee> employees { get; set; }
public DbSet<Project> projects { get; set; }
public DbSet<ProjectStep> projectSteps { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Employee>().HasMany(e => e.Projects).WithMany(t => t.EmployeesWorkingOnProject).Map(m =>
{
m.MapLeftKey("EmployeeId");
m.MapRightKey("ProjectId");
m.ToTable("employee_project");
});
modelBuilder.Entity<ProjectStep>().HasRequired(p => p.Project).WithMany(s => s.ProjectSteps).Map(m => m.MapKey("Project")).WillCascadeOnDelete(true);
//modelBuilder.Entity<Project>().HasMany(p => p.ProjectSteps).WithRequired(ps => ps.Project).WillCascadeOnDelete(true);
modelBuilder.Entity<Employee>().HasOptional<Project>(e => e.LeaderOfProject).WithOptionalPrincipal(p => p.ProjectLeader).Map(m => m.MapKey("ProjectLeader"));
}
public Project FindProjectById(int id)
{
return this.projects.Find(id);
}
}
This is pretty much everything needed which plays into my issue.
I have set up a total of 3 model classes:
[DataContract(Namespace = "Shared")]
public class Employee
{
public Employee()
{
this.Projects = new List<Project>();
}
[DataMember]
public int ID { get; set; }
[DataMember]
public String Name { get; set; }
[DataMember]
public String JobDescription { get; set; }
[DataMember]
public String Department { get; set; }
[DataMember]
public String DirectDialing { get; set; }
[DataMember]
public bool Status { get; set; }
public virtual Project LeaderOfProject { get; set; }
[DataMember]
public virtual List<Project> Projects { get; set; }
}
[DataContract(Namespace = "Shared")]
public class Project
{
public Project()
{
this.EmployeesWorkingOnProject = new List<Employee>();
this.ProjectSteps = new List<ProjectStep>();
}
[DataMember]
public int ID { get; set; }
[DataMember]
public String Titel { get; set; }
[DataMember]
public DateTime StartDate { get; set; }
[DataMember]
public DateTime EndDate { get; set; }
[DataMember]
public String Description { get; set; }
[DataMember]
public Employee ProjectLeader { get; set; }
[DataMember]
public bool Status { get; set; }
[DataMember]
public virtual List<Employee> EmployeesWorkingOnProject { get; set; }
[DataMember]
public virtual List<ProjectStep> ProjectSteps { get; set; }
}
[DataContract(Namespace = "Shared")]
[Table("project_step")]
public class ProjectStep
{
[DataMember]
public int ID { get; set; }
[DataMember]
public String Description { get; set; }
[DataMember]
public DateTime StartDate { get; set; }
[DataMember]
public DateTime EndDate { get; set; }
[DataMember]
public Project Project { get; set; }
}
And the corresponding database setup:
Now to my problem. Whenever I execute the FindProjectById method it does return the proper object, but it is missing any reference to the childs. this means that
ProjectSteps
EmployeesWorkingOnProject
ProjectLeader
are not set. This also causes issues on my delete methods. I assume that this is an error in my OnModelCreating method, but I am not 100% sure.
Can anyone tell me what I am missing to fetch the child objects as well?
You have disabled lazy loading and proxies, but if you do not .Include() child entities then EF doesn't know to load them. To use .Include() you will need to use .SingleOrDefault() rather than Find. Otherwise you will need to go to the context to Load child collections/references.
Your FindProjectById() would look something like:
var project = this.Projects.Include(x=>x.ProjectLeader)
.Include(x=>x.EmployeesWorkingOnProject)
.Include(x=>x.ProjectSteps)
.SingleOrDefault(x=>x.ID == id);
return project;
A caveat around using SingleOrDefault vs. Find is that where Find will search local store then go to DB, Single/First/ etc. will go to DB. This means that a query is executed each time where Find may find an entity in the local memory store. if you're inserting records into the DB Context (and prior to save changes) searching for that entity will not return an entity that is in the local store, but hasn't been committed to the DB yet. (prior to SaveChanges())
Typically you do not want to return entities outside of the scope of the DbContext as lazy load proxies won't work so anything you don't pre-load will be #null. I generally rely on deferred execution, returning IQueryable then .Select() the various bits I care about into DTO/ViewModel POCO classes.
You can try change the lazy loading into true if you want to automatically load the child objects.
this.Configuration.LazyLoadingEnabled = true;
if the lazy loading is false, then you need to load the reference before accessing it.
Is it possible to have a foreign key mapping based on a specific column value.
I have the following entities.
public class Controller
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual List<ControllerDevice> ActiveDevices { get; set; }
public virtual List<ControllerDevice> TamperedDevices { get; set; }
public virtual List<ControllerDevice> IgnoredDevices { get; set; }
}
public class ControllerDevice
{
public int Id { get; set; }
public DeviceStatus Status { get; set; }
public int ControllerId { get; set; }
public int NetworkDeviceId { get; set; }
public virtual Controller Controller { get; set; }
public virtual NetowkDevice NetowkDevice { get; set; }
}
public class NetowkDevice
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
public enum DeviceStatus
{
Active,
Tampered,
Ignored
}
Is it possible to have the ActiveDevices, TamperedDevices and IngoredDevices list be auto populated based on ControllerDevice DeviceStatus, or would I have to create three different tables for each list. IE ActiveControllerDevice, TamperedControllerDevices and IgnoredControllerDevices.
Please let me know if you require further explanation.
Use single devices collection:
public class Controller
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual List<ControllerDevice> Devices { get; set; }
}
...and filter it, when you need to process or display devices with specific Status value:
controller.Devices.Where(d => d.Status == DeviceStatus.Active);
Several tables for each devices status, and/or devices hierarchy (theoretically, you can solve this problem with a TPH inheritance) is a way to hell, because instead of single entity ControllerDevice with a status you'll get three entity types (ActiveControllerDevice, TamperedControllerDevice and IgnoredControllerDevice), which is not corresponding to model.
Instead of changing status, the device will change its type, and you cannot do that in simple way.
public class TestContext : DbContext
{
public TestContext()
{
Configuration.AutoDetectChangesEnabled = true;
Configuration.LazyLoadingEnabled = true;
Configuration.ProxyCreationEnabled = true;
Configuration.ValidateOnSaveEnabled = true;
}
public virtual DbSet<NetowkDevice> NetowkDevices{ get; set; }
public virtual DbSet<ControllerDevice> ControllerDevices{ get; set; }
public virtual DbSet<Controller> Controlleres{ get; set; }
}
http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/d0443029-2175-4bde-a834-4f8dbf313201/
Should I enable or disable dynamic proxies with entity framework 4.1 and MVC3?
Yes, you can do that. Enum support was introduced in Entity Framework 5, .Net Framework 4.5. In Entity Framework, an enumeration can have the following underlying types: Byte, Int16, Int32, Int64 , or SByte.
And you can filter like this:
context.ControllerDevices.Where(d => d.Status == DeviceStatus.Active);
More here: http://msdn.microsoft.com/en-us/data/hh859576.aspx