Persist List of same Class - c#

As someone coming from JPA to C# .Net and Entity framework, I'm having a really hard time wrapping my head around the lack of support for lists in EF.
I need to do the following:
public class Foo {
public long Id {get; set;}
//Class value objects / attributes
//e.g
public string Name {get; set;}
public AnotherFoo AFoo {get; set;}
//This class can have many children classes of the same type
//In this case Foo can have many Foo's
public List<Foo> {get; set;}
}
However, I'm not sure how to go on persisting this. I've seen articles about Hierarchical Data Management and I tried implementing them but with no success. All went fine untill I had to query the 'List' then, I would get a recursive error which I don't remember exactly what it was.
I've also searched in stackoverflow, but could not pinpoint my problem.
TL;DR
I want to persist a class that has a list of itself in Entity Framework /.Net Core, how would I go on about it?
I'd really appreciate it if anyone could give me a hand on this! Thanks in advance.

The problem with your implementation is that you don't describe your schema to Entity Framework, i.e you don't define the relationships between you objects.
Here is a suggestion that works:
public class Foo
{
public int ID { get; set; }
public string Name { get; set; }
[ForeignKey("AFoo")]
public int? AFooID { get; set; }
public virtual AnotherFoo AFoo { get; set; }
public int? ParentID { get; set; }
public virtual Foo Parent { get; set; }
public virtual List<Foo> Children { get; set; } = new List<Foo>();
}
public class AnotherFoo
{
public int ID { get; set; }
public string Name { get; set; }
}
Please note a couple of things:
I have added a foreign key property AFooID to the AFoo entity. Entity Framework will recognize on its own the relationship between AFoo property and Foo entity and when building the schema it will also add a foreign key to AntoherFoo entity. But this key will not be available in your code because you didn't add it to the Foo class properties. Also, it is better that you have the complete control over the scema creation and don't let much to Entity Framework's imagination (which is quite good nevertheless).
A have added an annotation [ForeignKey("AFoo")] over AFooID to define that this is a foreign key to AFoo navigation parameter. Normally I should also add a similar annotation over ID to define it as a primary key, but Entity Framework does this automatically for an integer property named ID.
I have used the virtual keyword on all navigation parameters to allow lazy loading of those parameters.
Now, for your prime question; You can create a reference of children of the same type as if it was any other type. But you have to describe the relationship! In this example I configure an one to many relationship, meaning that each foo can have only one parent but many children. To do this I used Fluent API:
public class MyContext: DbContext
{
public DbSet<Foo> Foos { get; set; }
public DbSet<AnotherFoo> AFoos { get; set; }
public MyContext()
: base("TestDB")
{
Configuration.LazyLoadingEnabled = true;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Foo>()
.HasOptional(f => f.Parent)
.WithMany(f => f.Children)
.HasForeignKey(f => f.ParentID)
.WillCascadeOnDelete(false);
}
}
Override the OnModelCreating method of your DbContext class to specify more "fluently" your database schema.
Now run the following commands in your Package Manager Console to create your database:
add-migration InitialDB
It will create a cs file that describes the schema to be created. Check that is as you desired.
update-database
It will create a database using the connection string defined in your config file and the schema from the file above.
Run this demo application:
static void Main(string[] args)
{
var foo = new Foo { Name = "parent foo" };
var foo1 = new Foo { Name = "first foo child" };
var foo2 = new Foo { Name = "second foo child" };
foo.Children.Add(foo1);
foo.Children.Add(foo2);
using(var context = new MyContext())
{
context.Foos.Add(foo);
context.SaveChanges();
}
// another transaction to read the saved data
using(var context = new MyContext())
{
var readfoo = context.Foos.FirstOrDefault();
Console.WriteLine($"{readfoo.Name} has the following children:");
foreach(var child in readfoo.Children)
Console.WriteLine(child.Name);
}
Console.ReadKey();
}
The results is as expected:
I hope I helped you solve your problem and understand a bit more the world of Entity Framework.

Related

Entity framework Core - The instance of entity type cannot be tracked because another instance with the key value is already being tracked

Trying to establish a many to many relationship in my db context, I got the following error while seeding data:
The instance of entity type 'Folder' cannot be tracked because
another instance with the key value '{Id: folder001 }' is
already being tracked. When attaching existing entities, ensure that
only one entity instance with a given key value is attached.
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<UserFolder>(userFolder =>
{
userFolder.HasKey(ud => new { ud.UserId, ud.FolderId });
userFolder.HasOne(uf => uf.Folder)
.WithMany(f => f.Users)
.HasForeignKey(uf => uf.FolderId)
.IsRequired();
userFolder.HasOne(uf => uf.User)
.WithMany(f => f.Folders)
.HasForeignKey(uf => uf.UserId)
.IsRequired();
});
}
_
public class Folder
{
public string Id { get; set; }
public string FolderName { get; set; }
public virtual ICollection<UserFolder> Users { get; set; }
}
_
public class User
{
public string Id { get; set; }
public string UserName { get; set; }
public virtual ICollection<UserFolder> Folders { get; set; }
}
_
public class UserFolder
{
public string UserId { get; set; }
public virtual User User { get; set; }
public string FolderId { get; set; }
public virtual Folder Folder { get; set; }
}
Searching this error results mostly to answers talking about the need to use Scoped instead of Singleton to register the service, but here I am configuring my service this way: services.AddDbContext<DataContext>, also some answers say that it is needed to add .AsNoTracking().
So how can I get rid of this error ?
You have not actually posted the code that is relevant to the error, but I can explain the cause of the error.
Entity Framework has a changetracker, which means that a db context has a sort of "secret dictionary" of entities it knows about, which it uses like a cache to cut down on database calls and keep track of what you do and don't change about these entities. These entities are stored and referenced in the change tracker using their primary key.
Entity Framework can only store one object of a given type and a given PK value. It cannot track two distinct object that have the same value.
Think of it as if it were a dictionary, which can only store one value for a given key value. A dictionary happily overwrites one values with another, but EF instead refuses to do so as this is so very likely to lead to unexpected behavior and hard to troubleshoot bugs.
The following code will throw the same error as you're experiencing:
using(var db = new MyContext())
{
Foo foo1 = new Foo() { Id = 123 };
Foo foo2 = new Foo() { Id = 123 }; // different object in memory, same PK identifier
db.Foos.Attach(foo1);
db.Foos.Attach(foo2); // exception!
}
I cannot tell you where in your code this error is being caused since you did not post the code in which this is may be happening.

EF Core - Reuse the same join table entity for several relationships

I want to reuse the same many-to-many relationship table (FileInEntity) for several other objects (Course, Lecture, Game), since they all can have files. Since we have to manually create the many-to-many relationships by creating a join entity, I want to reuse the join entity for the objects (Course, Lecture, Game).
If we look at the table structure, I would like to have the following:
Course: Id, ...
Lecture: Id, ...
Game: Id, ...
FileInEntity: EntityId (this can be either Course.Id, Lecture.Id or Game.Id), FileId
File: Id, ...
(File is base class type with two derived types: Image and Audio)
When I try this approach in .NET Core, I receive the following error message:
Entity type 'FileInEntities' is in shadow-state. A valid model requires
all entity types to have corresponding CLR type.
Is this even possible?
This is my setup:
ModelBase.cs
public class ModelBase
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
}
Course.cs
[Table("Courses")]
public class Course : ModelBase
{
private ICollection<FileInEntity> IconsInCourse { get; set; } = new List<FileInEntity>();
[NotMapped]
public File Image => IconsInCourse.Select(e => e.File).FirstOrDefault();
}
Lecture.cs
// Same as Course.cs
Game.cs
// Same as Course.cs
FileInEntity.cs
[Table("FilesInEntities")]
public class FileInEntity
{
public Guid FileId { get; set; }
public Guid EntityId { get; set; }
public virtual ModelBase Entity { get; set; }
public virtual File File { get; set; }
}
File.cs
[Table("Files")]
public class File : ModelBase
{
// This is the property for which the error occured
private ICollection<FileInEntity> FileInEntities { get; set; } = new List<FileInEntity>();
public IEnumerable<ModelBase> Entities => FileInEntities.Select(e => e.Entities);
}
FilesInEntitiesMap.cs (Relationship Configuration)
builder.HasOne(p => p.Entity)
.WithMany()
.HasForeignKey(k => k.EntityId);
builder.HasOne(p => p.File)
.WithMany()
.HasForeignKey(k => k.FileId);
FileMap.cs
// This is the key to which the error references to
builder.HasMany("FileInEntities")
.WithOne("Entity")
.HasForeignKey("EntityId");
You won't be able to use the base class ModelBase as the object in the mapping class because c# wont know the actual type coming back from the db. You can look at table per hierarchy inheritance, but I'm still not sure you would be able to use that in a mapping table either. Here is a good article
If your Course.cs, Lecture.cs, and Game.cs are the same and the only difference is type, you could combine them into one class and add an enum property to set the type.
public enum EntityType{
Game = 1,
Lecture = 2,
Course = 3
}
public class MyEntity : ModelBase{
private ICollection<FileInEntity> Icons { get; set; } = new List<FileInEntity>();
[NotMapped]
public File Image => Icons.Select(e => e.File).FirstOrDefault();
public EntityType EntityType {get;set;} //course, lecture, or game
}
When you care about the type just use a where clause.
Be sure to use Fluent Api in DbContext's OnModelCreating to determine One to One relationship for this tables. (and be sure again correct reference properties are selected)
Missing parts of your codes.
public class ModelBase
{
[Key]// add for primary key
//set none always for primary keys (because guid has no auto increment)
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public Guid Id { get; set; }
}
[Table("Files")]
public class File : ModelBase
{
//make it public
public ICollection<FileInEntity> FileInEntities { get; set; } = new List<FileInEntity>();
[NotMapped] //set not mapped
public IEnumerable<ModelBase> Entities => FileInEntities.Select(e => e.Entities);
//do it same changes for `Lacture.cs`, `Game.cs` and `Course.cs`...
}

AutoMapper - ProjectTo class with multiple properties of the same type

Recently found out about AutoMapper's ProjectTo<> method, so I've been playing around with it.
So far so good until I came upon a class that had multiple properties of the same type, such as:
public class RandomDto
{
public int Id {get;set;}
public ChildDto FirstChild {get;set;}
public ChildDto SecondChild {get;set;}
}
It seems like it generates SQL for a single Child relationship, and not for both:
SELECT CASE
WHEN [dtoRandom].[FirstChild_FK] IS NULL
THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT)
END, [t0].[Child_Description]
END, [dtoRandom].[Id]
FROM [Randoms] AS [dtoRandom]
INNER JOIN (
SELECT [s].*
FROM [Childs] AS [s]
) AS [t0] ON [dtoRandom].[FirstChild_FK] = [t0].[Id]
I've tried
.ProjectTo<RandomDto>(null, "FirstChild", "SecondChild")
.ProjectTo<RandomDto>(x=>x.FirstChild, x=>x.SecondChild)
and both return with the first one being filled in, second being null
Not sure if I need to set custom aliases or something for this to work?
I experienced the same behaviour and tried to work around the problem by using an other class for the second navigation property that only derived from the original one.
public class ChildDtoTmp : ChildDto { }
public class RandomDto
{
public int Id { get; set; }
public ChildDto FirstChild { get; set; }
public ChildDtoTmp SecondChild { get; set; }
}
This worked fine, but because the type of the navigation proerty has another navigation property the same strange behaviour shows up with the nested navigation property.
public class ChildDto
{
public int Id { get; set; }
public InnerChildDto InnerChild { get; set; }
}
This leads to the properties FirstChild and SecondChild being mapped, but only the InnerChild property of the FirstChild gets mapped.
Maybe this helps someone to figure out how to solve this.
Your issue might be related to a slightly confusing option called MaxDepth. I've managed to reproduce that exact problem by calling MaxDepth(1), which in my understanding should only affect self-referencing entities such as:
class Foo
{
public Foo InnerFoo { get; set; }
}
In that case, a MaxDepth(1) should only map the first Foo found in a object graph. Which is exactly what it happens, but it also affects the following structure (quite wrongly, IMHO):
class Bar
{
public Foo Foo1 { get; set; }
public Foo Foo2 { get; set; }
}
A MaxDepth(1) in the above scenario will map only the Foo1 property, keeping Foo2 as null.
Oh, by the way: to set MaxDepth, one may apply to all their mappings:
var mapperCfg = new MapperConfiguration(cfg =>
{
cfg.AddProfile<SomeProfile>();
cfg.ForAllMaps(SetMaxDepth);
});
private static void SetMaxDepth(TypeMap typeMap, IMappingExpression expression) => expression.MaxDepth(1);
Or to each map individually:
var mapperCfg = new MapperConfiguration(cfg =>
{
cfg.CreateMap<SomeObject, SomeObjectDto>().MaxDepth(1);
});

How to create tables in codefirst approach

I am working on Login page creating in C# using Code First approach , in this I am getting lot of errors while trying to execute the code. I am a fresher and new to this .
Can you please review my code and help me what I missed in this ?
Rule are not creating and getting the multiple errors. Your help would help me to understand what went wrong in this.
My Class "Class1.cs"
public class Login
{
[Required]
public string username { get; set; }
[Required]
public string password{ get; set; }
}
}
public class LoginContext : DbContext
{
public LoginContext() : base("LoginDBConnectionString")
{
Database.SetInitializer<LoginContext>(new DropCreateDatabaseIfModelChanges<LoginContext>());
}
public DbSet<username> username { get; set; }
public DbSet<password> password { get; set; }
}
Context.cs
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Linq;
using System.Web;
using System.Web.UI.WebControls;
using System.Data.Entity;
using Jquery1.Models;
namespace Jquery1.Models
{
public class Logincontext: DbContext
{
public Logincontext () : base ("LoginDBConnectionString")
{
}
public DbSet<Login> Logins{ get; set; }
}
}
class program
{
static void Main(string[] args)
{
using (var ctx = new Logincontext())
{
ctx.Database.Create();
}`enter code here`
}
}
Hi Let me explain this using fluent api bear with me a little please,
Create Your DbContext First:
public class MyDbContext : DbContext
{
public MyDbContext()
: base("name=MyConnection")
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyDbContext, YourApplication.Migrations.Configuration>("MyConnection"));
}
public DbSet<Users> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//here you can MAP Your Models/Entities, but i am going to show you something more interesting. so keep up.
modelBuilder.Configurations.Add(new UsersMap());
}
}
Create a Migration Folder in your app root And make Configuration class there(Why?! so that everytime you made a change migration of EF will update the Tables for you):
internal sealed class Configuration : DbMigrationsConfiguration<YourApplication.Infrastructure.Data.MyDbContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
//this feature on true can result in unwanted/unexpected dataloss
AutomaticMigrationDataLossAllowed = true;
ContextKey = "YourApplication.Infrastructure.Data.MyDbContext";
}
protected override void Seed(YourApplication.Infrastructure.Data.MyDbContext context)
{
// This method will be called after migrating to the latest version.
// You can use the DbSet<T>.AddOrUpdate() helper extension method
// to avoid creating duplicate seed data. E.g.
//
// context.People.AddOrUpdate(
// p => p.FullName,
// new Person { FullName = "Andrew Peters" },
// new Person { FullName = "Brice Lambson" },
// new Person { FullName = "Rowan Miller" }
// );
//
}
}
Now Go on And Create Your POCO Classes. I try to write my codes very clean. That's why when for example i made a Model like below, i create an EntityBase for every Id:
public class EntityBase
{
public int Id { get; set; }
}
And Implement it to my Model :
public class User: EntityBase
{
public string Example1{ get; set; }
public string Example2{ get; set; }
public string Example3{ get; set; }
}
And For Mapping I Create another Class like below and use Fluent Api:
public class UserMap : EntityTypeConfiguration<User>
{
public UserMap()
{
//declaring the table name
ToTable("TblUser");
//declaring primary key of the table
HasKey(x => x.Id);
//declaring a property from poco class is required
Property(x => x.Example1)
.IsRequired();
//etc
}
}
be aware if you are using fluent api, you Shouldn't use Data Annotations. Happy Coding.
Entity frame-work uses a concept of standards-or-else. If you want your items fairly standard, you don' have to provide a lot of information. If you want your tables to have different names, or your columns to be different than standard you'll have to provide extra information using either Attributes (the method you use) or fluent API.
Every class that should become a table should have an Primary key. The default is to give your class a property Id, or to give the property the name of your class followed by Id:
public class Login
{
public int Id {get; set;}
public string UserName {get; set;}
public string Password {get; set;}
}
public class MyDbContext : DbContext
{
public DbSet<Login> Logins {get; set;}
}
This should be enough to give you a table with the default name, which is the plural of your class name Logins. Each record in the table has three columns:
Id: the primary key
UserName: a string, which in this case may be null
PassWord: a string which may be null.
Your Requires attribute will ensure that your UserName and Property are not null, but they won't prevent them being empty. I'm not sure that's enough for you.
Instead of Id your are free to use LoginId as foreign key:
public int LoginId {get; set;}
You'll see both methods used. Both have their advantages. The use of Id shows you immediately which property is the primary key. LoginId could also be used as foreign key. The name LoginId alone is not enough to see whether it is a primary key or a foreign key.
The defaults are usually plurals for collections of items where the item is the singular form. Here you'll see Login as class, and Logins as a set of objects of this class.
The article that helped me a lot to get on track using Entity Framework was this entity framework tutorial
The tutorial tells you about how to use defaults, how to create one-to-many relations with foreign keys, many-to-many, various inheritance strategies, and what to do if you are not satisfied with a default model.

Querying by base type in EF4 Code-First

I have the following types:
public abstract class Vehicle {
public int Id { get; set; }
public double TopSpeed { get; set; }
}
public class Car : Vehicle {
public int Doors { get; set; }
}
public class Motorcycle : Vehicle {
public string Color { get; set; }
}
And I have a code-first DBContext:
public MyDbContext: DbContext {
public DbSet<Car> Cars { get; set; }
public DbSet<Motorcycle> Motorcycles { get; set; }
}
This works great if I query for a car or moto directly...
var dbContext = new MyDbContext();
var cars = dbContext.Set<Car>().Where(x=> x.TopSpeed>10); // <-- THIS WORKS
But if I want a list of all vehicles, whether car or moto, I would like to do this:
var dbContext = new MyDbContext();
var vehicles = dbContext.Set<Vehicle>().Where(x=> x.TopSpeed>10); // <-- THIS DOES NOT WORK
When I try the above code, I get an exception:
System.InvalidOperationException : The entity type Vehicle is not part
of the model for the current context.
That makes perfect sense... I didn't add Vehicle to the context. I added car and moto. At this point, I'm not sure what to do. I tried adding Vehicle to my context, but that combined the tables for car and moto into one Vehicle table. I definitely want a separate table for cars and motos (and possibly a table for the vehicle base properties as well). What's the best way to do this?
Have a Vehicles Properties of type DBSet of Vehicle in your MyDbContext class.
public MyDbContext: DbContext {
public DbSet<Car> Cars { get; set; }
public DbSet<Motorcycle> Motorcycles { get; set; }
public DbSet<Vehicle> Vehicles { set; get; }
}
Now you can access all vehicles with the criteria like this
var vehicles = dbContext.Set<Vehicle>().Where(x=> x.TopSpeed>10);
Keep in mind that you should have a Key Property in your entity classes ( ID or {ClassName}ID). Otherwise it is going to give you a run time error! (Yes the code will compile.)
public abstract class Vehicle
{
public int ID { set; get; }
public double TopSpeed { get; set; }
}
EDIT : As per the comment
By Default Entity Framework will do a Table Per Hierarchy.All Data in the hierarchy is saved in a single table and it use the Discriminator column to identify which record belongs to which subtype. So As the result you will have one Vehicle table with columns same as properties of All your classes in the hierarchy with an extra column called "Discriminator". For a Record for Car, It will have the "Car" value inside the Discriminator column.
If you want to create Single table per each type, We will go for Table Per Type. Entity Framework will create a Table for the base class and seperate table for all child classes.
To make this happen, You can use Fluent API to override the configuration.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>().ToTable("Cars");
modelBuilder.Entity<Motorcycle>().ToTable("Motorcycles");
base.OnModelCreating(modelBuilder);
}
And the output is
Now You should be able to query Your Vehicle Entries like this
var vehicles = dbContext.Set<Vehicle>().Where(x => x.TopSpeed > 150).ToList();
And the result is here
Notice that the result contains both MotorCycle type and Car type.
Check this link to decide what Inheritance Strategy to use.
Have you tried giving Vehicle its own context with just a DBSet? I think that might do it for you.
Using a DataAnnotation.Schema.Table("TableName") entry, on the inherited class, triggers the creation of a new table for the inherited type (table-per-type) and eliminates the Discriminator field in the parent type table, without the need for the Fluent API code.

Categories