I have a problem with EF Core code first class object mapping. I have a class Match where I have properties for Team A and Team B.
public class Match: MyEntity
{
public virtual Team TeamA { get; private set; }
public virtual Team TeamB { get; private set; }
public GameType GameType { get; private set; }
public MatchStatus Status { get; private set; }
public Match()
{
}
Here my Team entity, where I have reference on MatchId.
public class Team: MyEntity
{
public virtual int MatchId { get; private set; }
private Team()
{ }
...
}
So I need MatchId be the same for TeamA and TeamB in case they are in the same match.
When I am trying to create migration, I have an error:
Both relationships between 'Team' and 'Match.TeamA' and between 'Team' and 'Match.TeamB' could use {'MatchId'} as the foreign key. To resolve this, configure the foreign key properties explicitly in 'OnModelCreating' on at least one of the relationships.
builder.Entity<Team>()
.HasOne<Match>()
.WithOne(x => x.TeamA)
.HasForeignKey<Team>(x => x.MatchId);
builder.Entity<Team>()
.HasOne<Match>()
.WithOne(x => x.TeamB)
.HasForeignKey<Team>(x => x.MatchId);
When I am using this configuration it works, but it is deleting in migration TeamAId and TeamBId columns from Matches table and creates two columns in Team table: MatchId and MatchId1.
migrationBuilder.DropColumn(
name: "TeamAId",
table: "Matches");
migrationBuilder.DropColumn(
name: "TeamBId",
table: "Matches");
migrationBuilder.AddColumn<int>(
name: "MatchId",
table: "Teams",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "MatchId1",
table: "Teams",
type: "int",
nullable: true);
Maybe am I missing something?
How should I fix this ?
Thank you for any help.
I've fixed it with not relying on default EF Core configurations, but on mine in OnModelCreating. Hope I did it in right way, someone can tell if see something wrong.
builder.Entity<Team>(b =>
{
b.HasOne<Match>()
.WithOne(x => x.TeamA)
.HasForeignKey<Team>(x => x.MatchId);
b.HasOne<Match>()
.WithOne(x => x.TeamB)
.HasForeignKey<Team>(x => x.MatchId);
});
builder.Entity<Match>(b =>
{
b.HasOne<Team>("TeamA")
.WithMany()
.HasForeignKey("TeamAId");
b.HasOne<Team>("TeamB")
.WithMany()
.HasForeignKey("TeamBId");
b.Navigation("TeamA");
b.Navigation("TeamB");
});
Related
I've been messing around with EF Core 3.1 trying to setup a relationship between two classes. The trick is the composite key on the one class which the other one is only related to through one part.
Consider the following:
public class A
{
public string Foo { get; set; }
public long Id { get; set; }
public IEnumerable<B> B { get; set; }
}
public class B
{
[Key]
public int Id { get; set; }
public string Bar { get; set; }
}
...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<A>(b =>
{
b.HasKey(x => new {x.Id, x.Foo});
b.HasMany(x => x.B)
.WithOne()
.HasPrincipalKey(x => x.Id);
});
}
Which creates the following migration:
migrationBuilder.CreateTable(
name: "A",
columns: table => new
{
Foo = table.Column<string>(nullable: false),
Id = table.Column<long>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_A", x => new { x.Id, x.Foo });
table.UniqueConstraint("AK_A_Id", x => x.Id);
});
migrationBuilder.CreateTable(
name: "B",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Bar = table.Column<string>(nullable: true),
AId = table.Column<long>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_B", x => x.Id);
table.ForeignKey(
name: "FK_B_A_AId",
column: x => x.AId,
principalTable: "A",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_B_AId",
table: "B",
column: "AId");
Which is almost what I want, except for the UniqueConstraint on table A! Is there a way around this?
This is not possible. And not because of EF Core, but the definition of the relationship multiplicity (cardinality).
The direct FK relationship from dependent to principal is to (has multiplicity) one (1) (or zero-or-one 0..1 in case of optional relationship), i.e. the FK must identify exactly one principal record. That's why the referenced principal key must be unique, which is satisfied by primary or unique key constraint.
What you are asking allows dependent record referencing more than one (i.e. many) matching principal records. In other words, to many cardinality. Which cannot be achieved with FK in the dependent table, but would require the typical for many-to-many relationship intermediate link (join) entity/table.
(Sort of like this old SO post, but for EF Core 2 or 3)
When I map two independent entities that have an optional 1-to-1 bi-directional navigation to each other, the migration generated by EF Core is missing one of the foreign keys I would expect to see.
I have the following classes:
public class ClassOne
{
public int Id { get; set; }
public ClassTwo ClassTwo { get; set; }
public int? ClassTwoId { get; set; }
}
public class ClassTwo
{
public int Id { get; set; }
public ClassOne ClassOne { get; set; }
public int? ClassOneId { get; set; }
}
And the following mapping definition:
public class ClassOneDbConfiguration : IEntityTypeConfiguration<ClassOne>
{
public void Configure(EntityTypeBuilder<ClassOne> builder)
{
builder.HasOne(e => e.ClassTwo)
.WithOne(e => e.ClassOne)
.HasForeignKey<ClassTwo>(e => e.ClassOneId)
.OnDelete(DeleteBehavior.SetNull);
builder.HasIndex(e => e.ClassTwoId).IsUnique();
}
}
public class ClassTwoDbConfiguration : IEntityTypeConfiguration<ClassTwo>
{
public void Configure(EntityTypeBuilder<ClassTwo> builder)
{
builder.HasOne(e => e.ClassOne)
.WithOne(e => e.ClassTwo)
.HasForeignKey<ClassOne>(e => e.ClassTwoId)
.OnDelete(DeleteBehavior.SetNull);
builder.HasIndex(e => e.ClassOneId).IsUnique();
}
}
The migration generated by dotnet-ef migrations add CreateLinks gives (I created the tables in a separate migration to simplify):
migrationBuilder.AddColumn<int>(
name: "ClassOneId",
table: "EntitiesTwo",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "ClassTwoId",
table: "EntitiesOne",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_EntitiesTwo_ClassOneId",
table: "EntitiesTwo",
column: "ClassOneId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_EntitiesOne_ClassTwoId",
table: "EntitiesOne",
column: "ClassTwoId",
unique: true);
migrationBuilder.AddForeignKey(
name: "FK_EntitiesOne_EntitiesTwo_ClassTwoId",
table: "EntitiesOne",
column: "ClassTwoId",
principalTable: "EntitiesTwo",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
Where is the Foreign Key creation for table EntitiesTwo? Without it, I cannot have the ON CASCADE SET NULL on it and may end up with invalid data in my DB.
I can ensure referential integrity in my code, sure, but can I have the foreign key on the DB as well?
I'm trying to model and generate a database with inheritance, using entity framework core 3.0.
I'm following the guide in https://www.learnentityframeworkcore.com/inheritance/table-per-hierarchy
Im my code, I have 3 classes: Parent is "Source", and to inherited children "Publication" and "Feeder"
The guide tells that all field from child classes will be in the global table. But they are not.
My model is the following (one parent class, two children)
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Legal.Models {
public enum SourceType { Publication, Feeder }
// An issuer can have one or more sources. Every source has a type among 3 types
[Table("Source")]
public class Source {
// PK
public int SourceId { get; set; }
// Attributes
public SourceType SourceType { get; set; }
public string Description { get; set; }
}
[Table("Source")]
public class Publication : Source {
[MaxLength(13)]
public string ISSN;
[MaxLength(2048)]
public string IssueTocUrl;
// Generate FK in Issues
// public List<Source> Sources { get; } = new List<Source>();
}
[Table("Source")]
public class Feeder : Source {
[MaxLength(2048)]
public string FeederUrl;
}
}
I generate the migration using the following command line
dotnet ef migrations add Version_0_0
It generates the following designer code (that seems correct - check source, feeder and publication entities)
// <auto-generated />
using System;
using Legal.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Legal.Migrations
{
[DbContext(typeof(LegalDbContext))]
[Migration("20191022181406_Version_0_0")]
partial class Version_0_0
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Legal.Models.Issuer", b =>
{
b.Property<int>("IssuerId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Description")
.HasColumnType("varchar(64)")
.HasMaxLength(64);
b.Property<string>("LandPageUrl")
.HasColumnType("longtext")
.HasMaxLength(2048);
b.HasKey("IssuerId");
b.ToTable("Issuer");
});
modelBuilder.Entity("Legal.Models.Source", b =>
{
b.Property<int>("SourceId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Description")
.HasColumnType("longtext");
b.Property<string>("Discriminator")
.IsRequired()
.HasColumnType("longtext");
b.Property<int?>("IssuerId")
.HasColumnType("int");
b.Property<int>("SourceType")
.HasColumnType("int");
b.HasKey("SourceId");
b.HasIndex("IssuerId");
b.ToTable("Source");
b.HasDiscriminator<string>("Discriminator").HasValue("Source");
});
modelBuilder.Entity("Legal.Models.Feeder", b =>
{
b.HasBaseType("Legal.Models.Source");
b.ToTable("Source");
b.HasDiscriminator().HasValue("Feeder");
});
modelBuilder.Entity("Legal.Models.Publication", b =>
{
b.HasBaseType("Legal.Models.Source");
b.ToTable("Source");
b.HasDiscriminator().HasValue("Publication");
});
modelBuilder.Entity("Legal.Models.Source", b =>
{
b.HasOne("Legal.Models.Issuer", null)
.WithMany("Sources")
.HasForeignKey("IssuerId");
});
#pragma warning restore 612, 618
}
}
}
But the generation script only include the source table WITHOUT any of the child fields
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Legal.Migrations
{
public partial class Version_0_0 : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Issuer",
columns: table => new
{
IssuerId = table.Column<int>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
Description = table.Column<string>(maxLength: 64, nullable: true),
LandPageUrl = table.Column<string>(maxLength: 2048, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Issuer", x => x.IssuerId);
});
migrationBuilder.CreateTable(
name: "Source",
columns: table => new
{
SourceId = table.Column<int>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
SourceType = table.Column<int>(nullable: false),
Description = table.Column<string>(nullable: true),
Discriminator = table.Column<string>(nullable: false),
IssuerId = table.Column<int>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Source", x => x.SourceId);
table.ForeignKey(
name: "FK_Source_Issuer_IssuerId",
column: x => x.IssuerId,
principalTable: "Issuer",
principalColumn: "IssuerId",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_Source_IssuerId",
table: "Source",
column: "IssuerId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Source");
migrationBuilder.DropTable(
name: "Issuer");
}
}
}
NOTICE:
I have tried
- With no annotatios
- With data annotations configuring 3 tables (Source, Publication and Feeder)
- With data annotations configuring only 1 table (Source - current example)
- With DbSet per every class
- Overriding OnModelCreating
// Define the table based collections
public DbSet<Issuer> Issuers { get; set; }
// public DbSet<Source> Sources { get; set; }
public DbSet<Publication> Publications { get; set; }
public DbSet<Feeder> Feeders { get; set; }
// Force creation of child Sources
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<Publication>();
modelBuilder.Entity<Feeder>();
}
you have the same name for all the table attributes: [Table("Source")]
sorry to make it unclear, but this should bring you some informations https://learn.microsoft.com/de-de/ef/core/modeling/relational/inheritance -> bascially all your tables get generated into the "Source" table and are only separated by the Discriminator flag.
See -> Discriminator = table.Column<string>(nullable: false), in the generated Source Table
Example from the Link, where the Inherited Items are separated by the Discriminator:
if you give them all different table names like i stated above you can just query them by the table name -> SELECT * FROM Sources WHERE Discriminator = "Source" would give you all data from the "Source" table
with the dbset like you had
public DbSet<Publication> Publications { get; set; }
public DbSet<Feeder> Feeders { get; set; }
and you query on them you will get the right items from the database.
just change the [Table("Source")] to something useful
I have found my (stupid) error.
I forgot to declare the setter/getter. The process only generate fields with get/set.
This is the right code
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Legal.Models {
public enum SourceType { Publication, Feeder }
// An issuer can have one or more sources. Every source has a type among 3 types
public abstract class Source {
// PK
public int SourceId { get; set; }
// Attributes
public SourceType SourceType { get; set; }
[MaxLength(256)]
public string Description { get; set; }
[MaxLength(1024)]
public string ServiceClass { get; set; }
// Parent/Many to One relation shipp
public int IssuerId { get; set; }
public Issuer Issuer { get; set; }
}
public class Publication : Source {
[MaxLength(13)]
public string ISSN { get; set; }
[MaxLength(2048)]
public string IssueTocUrl { get; set; }
// Generate FK in Issues
public List<PublicationIssue> PublicationIssues { get; set; }
}
public class Feeder : Source {
[MaxLength(2048)]
public string FeederUrl { get; set; }
}
}
I am building a new project for browsing through movies and giving your opinion for them. Now I am on the administration part and I added functionality for adding a movie but when I try to add a movie the automapper throws exception for unmapped members on the service where I am mapping dto to data model. The members are from the base data model for example the id.
EDIT:
I tried to ignore all the members that make this exception, also tried to made a constructor with no arguments but doesn't work.
// Initialization
Mapper.Initialize(conf =>
{
conf.CreateMap<Movie, MovieDto>();
conf.CreateMap<MovieDto, Movie>();
conf.CreateMap<MovieDto, MovieViewModel>();
});
// Base Data Model
public class DataModel
{
[Key]
public int Id { get; set; }
[DataType(DataType.DateTime)]
public DateTime? CreatedOn { get; set; }
[DataType(DataType.DateTime)]
public DateTime? ModifiedOn { get; set; }
public bool IsDeleted { get; set; }
[DataType(DataType.DateTime)]
public DateTime? DeletedOn { get; set; }
}
// Movie Data Model
public class Movie: DataModel
{
public Movie(string title, double rating, string duration, string type, string description, DateTime releaseDate, string producer)
{
this.Title = title;
this.Rating = rating;
this.Duration = duration;
this.Type = type;
this.Description = description;
this.ReleaseDate = releaseDate;
this.Producer = producer;
}
// successfully mapped members
}
// Movie DTO
public class MovieDto
{
public string Title { get; set; }
public double Rating { get; set; }
public string Duration { get; set; }
public string Type { get; set; }
public string Description { get; set; }
public DateTime ReleaseDate { get; set; }
public string Producer { get; set; }
}
// Add functionality
public void AddMovie(MovieDto movie)
{
//execption here
var movieDM = this.mapper.Map<Movie>(movie);
this.repo.Add(movieDM);
this.saver.SaveChanges();
}
This is the exception on img: https://i.imgur.com/RGZP6NP.png
Got it to work by doing the following.
Firstly, since DataModel is a base class, I followed automapper's mapping inheritance (see docs).
Then since you are using a mapper instance to map this.mapper.Map<Movie>(movie), the configuration needs to be instance rather than static as well, and I use the AutoMapper.Extensions.Microsoft.DependencyInjection nuget package for this, which allows registering Automapper with the IoC container.
My configuration looks like this (inside the ConfigureServices method of the Startup class).
services.AddAutoMapper(conf =>
{
conf.CreateMap<object, DataModel>()
.ForMember(d => d.Id, opts => opts.Ignore())
.ForMember(d => d.CreatedOn, opts => opts.MapFrom(_ => DateTime.Now))
.ForMember(d => d.ModifiedOn, opts => opts.MapFrom(_ => DateTime.Now))
.ForMember(d => d.DeletedOn, opts => opts.MapFrom(_ => (DateTime?)null))
.ForMember(d => d.IsDeleted, opts => opts.MapFrom(_ => false))
.Include<MovieDto, Movie>();
conf.CreateMap<Movie, MovieDto>();
conf.CreateMap<MovieDto, Movie>();
});
Note that I used CreateMap<object, DataModel> for the base class mapping and just used hardcoded values for dates there, feel free to adjust to suit your scenario.
After injecting an instance of IMapper, I was able to call this.mapper.Map<Movie>(movie) successfully.
Hope this sets u off in a good direction.
You can specify that AutoMapper should not validate that all properties are being mapped. The MemberList enum can be used for this when creating the mapping configuration. For example:
conf.CreateMap<MovieDto, Movie>(MemberList.None)
The error in the screenshot however indicates that another mapping is problematic, the one from MovieViewModel to MovieDto. I suggest you add a mapping configuration for these types as well:
conf.CreateMap<MovieViewModel, MovieDto>(MemberList.None)
You could try Profile Instances.
public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
CreateMap<OrderViewModel, Order>()
.ForMember(dest => dest.OrderItem, opt => opt.MapFrom(src => src.OrderItemViewModel));
CreateMap<OrderItemViewModel, OrderItem>();
CreateMap<Order, Order>()
.ForMember(dest => dest.Id, opt => opt.Ignore());
CreateMap<Movie, MovieDto>();
CreateMap<MovieDto, Movie>();
}
}
Here is the working demo AutoMapperProfile
TL;DR;
NHibernate reverse relationship is working on Azure-SQL and MSSQL2012 but not with SQLite
Description:
I am currently Unittesting my Asp.Net MVC App and set up my Unittest with FluentMigrator on SQLite.
After creating the Database I set up some base entries I need.
One of those is a Product.
A Product has many ProductSuppliers and a ProductSupplier has many ProductSupplierPrices
public class Product
{
public virtual long Id { get; set; }
public virtual string Number { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
//more properties
public virtual IList<ProductSupplier> Suppliers { get; set; }
//more properties
}
public class ProductSupplier
{
public virtual long Id { get; set; }
public virtual Product Product { get; set; }
public virtual Supplier Supplier { get; set; }
public virtual IList<ProductSupplierPrice> Prices { get; set; }
}
public class ProductSupplierPrice : IHaveId
{
public virtual long Id { get; set; }
public virtual ProductSupplier ProductSupplier { get; set; }
public virtual decimal FromAmount { get; set; }
public virtual decimal Price { get; set; }
}
Setup:
Create Supplier
Create Product
Create ProductSupplier
Create ProductSupplierPrice
Test:
Product product = this.session.Load<Product>((long)1);
ProductSupplier productSupplier = product.Suppliers.First(); //<-- Suppliers are null; therefore throws an exception
If I load them seperately to check the relationships:
productSupplierPrice.ProductSupplier <--- Correct Supplier
productSupplier.Prices <-- Null
productSupplier.Product <-- Product with Id 1
product.Suppliers <-- Null
So to me it seems, that the many-to-one direction works correctely, but the one-to-many (reverse relation) is not working.
The Problem exists only in my Unittest (SQLite) the App itself runs on Azure-SQL and is working fine.
EDIT:
Mappings with FluentnHibernate
public class ProductMap : ClassMap<Product>
{
public ProductMap()
{
Id(x => x.Id);
HasMany(x => x.Suppliers).Inverse().Cascade.DeleteOrphan().BatchSize(20);
//many more mappings
}
}
public ProductSupplierMap()
{
Id(x => x.Id);
References(x => x.Product);
References(x => x.Supplier);
Map(x => x.IsMainSupplier);
Map(x => x.SupplierProductNumber);
Map(x => x.CopperSurcharge);
HasMany(x => x.Prices).Inverse().Cascade.DeleteOrphan().BatchSize(20);
}
public ProductSupplierPriceMap()
{
Id(x => x.Id);
References(x => x.ProductSupplier);
Map(x => x.FromAmount);
Map(x => x.Price);
}
Edit2 - Creating the DB-Entries:
Product product = new Product()
{
Type = ProductType.Purchase,
Dispatcher = session.Load<Employee>(employeeId),
Number = "100.10-1000",
Name = "Testproduct",
//Lots of Properties
Suppliers = new List<ProductSupplier>()
};
session.SaveOrUpdate(product);
ProductSupplier productSupplier = new ProductSupplier()
{
Product = product,
Supplier = session.Load<Supplier>((long)1),
IsMainSupplier = true,
SupplierProductNumber = "Artikel123456",
CopperSurcharge = CopperSurchargeType.DEL700,
Prices = new List<ProductSupplierPrice>()
};
session.Save(productSupplier);
ProductSupplierPrice productSupplierPrice = new ProductSupplierPrice()
{
ProductSupplier = productSupplier,
FromAmount = 1,
Price = 5
};
session.Save(productSupplierPrice);
EDIT 3.1:
public static ISession InitializeDatabase()
{
NHibernateSessionHolder.CreateSessionFactory();
session = NHibernateSessionHolder.OpenSession();
CreateBaseEntries(); //Creates Employees, Supplier, Customer etc
return session;
}
Based on the Ayende's article you need to clear the session between insert/update and querying:
session.Clear();
Seems to be a session management, I'm not sure why the session should be clean, but the session is providing your original instance (the same you provided for saving, stored on the session cache) instead a proxy for lazy-loading.
private long CreatePurchaseOrder()
{
session.Clear();
var product = this.session.Load<Product>((long)1);
var productSupplier = product.Suppliers.First();
var productSupplierPrice = productSupplier.Prices.First();
return 0;
}
Sorry for late reply
In your unit test, you are using same session for creating and fetching entities. This is not right as subsequent fetch returns entities from first level cache which do not have their graph set up properly.
So....either use different sessions OR as a quick fix, I have added "session.Clear()" in the method "InitializeDatabase()" of "DatabaseSetUpHelper". Clearing the session clears first level cache and force NH to fetch data from DB again and the resulting entities have their graph set up properly.
public static ISession InitializeDatabase()
{
NHibernateSessionHolder.CreateSessionFactory();
session = NHibernateSessionHolder.OpenSession();
CreateBaseEntries();
session.Clear(); // notice this!!! this clears first level cache of session, thus forcing fetching of data from DB
return session;
}
Note: My quick-fix is not final solution, it is there just show how session behaves. In proper solution, you must use different sessions.