Add/update for related entity using aggregates. Having multiple domain classes - c#

This is my main domain Model.
public class Transform : IValue
{
public int Id { get; protected set; }
public string Target { get; set; }
public Transform() { }
}
}
And below is my Mapping Class.
Here I am creating table by using multiple domain classes like DefaultTransform, DirectTransform, InitialTransform and LoadTransform.
public class TransformMapping : IEntityTypeConfiguration<Transform>
{
public void Configure(EntityTypeBuilder<Transform> builder)
{
builder.ToTable("Transform");
builder.HasKey(x => x.Id);
builder.Property(x => x.Id).HasColumnName("TransformId").ValueGeneratedOnAdd();
builder.HasDiscriminator<string>("TransformType")
.HasValue<Transform>("Base")
.HasValue<DefaultTransform>("Default")
.HasValue<DirectTransform>("Direct")
.HasValue<InitialTransform >("Initial")
.HasValue<LoadTransform>("Load");
}
}
Below I have created Db context instance of Transform Class
public class TransRepository : DbContext
{
public virtual DbSet<Transform> Transforms { get; protected set; }
}
And want to add and update the data. But I am unable to get access of property of other domain classes like DefaultTransform, DirectTransform, InitialTransform and LoadTransform.
How can I update and add using above dbContext instance Transforms.

Related

How to implement Composite Pattern using TPT in EF Core?

I am using DDD to implement a domain and using EF Core and SQL as infrastructure for persistence. I have a model which is implemented by "Composite Design Pattern". I use fake name that does not show actual model but consider there is a School entity that has many Rooms:
public class Person: AggregateRoot<long>
{
public long Id { get; set; }
public string Name { get; set; }
private List<Document> _documents;
public IReadOnlyList<Document> Documents=> _documents.AsReadOnly();
}
Document is an abstract class that can be PersonDocument or CategoryDocument:
public abstract class Document: ValueObject
{
public string Title { get; set; }
}
and:
public class PersonDocument : Document
{
public string Code { get; set; }
}
public class CategoryDocument: Document
{
private List<Document> _documents;
public IReadOnlyList<Document> Documents=> _documents.AsReadOnly();
}
Three tables will be generated in SQL. Here is my mapping:
public override void EntityTypeConfiguration(EntityTypeBuilder<Person> builder)
{
builder.ToTable("Persons");
builder.HasKey(w => w.Id);
builder.Property(w => w.Id)
.ValueGeneratedNever();
builder.Property(x => x.Name).IsRequired().HasMaxLength(500);
}
public class DocumentMapping : IEntityTypeConfiguration<Document>
{
public void Configure(EntityTypeBuilder<Document> builder)
{
builder.Property<long>("Id").ValueGeneratedOnAdd();
builder.ToTable("Documents").HasKey("Id");
builder.UsePropertyAccessMode(PropertyAccessMode.Field);
builder.Property(x => x.Title).IsRequired().HasMaxLength(500);
builder.HasOne<Person>().WithMany(x => x.Documents).HasForeignKey("PersonId");
}
}
public class PersonDocumentMapping : IEntityTypeConfiguration<PersonDocument>
{
public void Configure(EntityTypeBuilder<PersonDocument> builder)
{
builder.Property<long>("Id").ValueGeneratedOnAdd();
builder.ToTable("PersonDocuments");
//builder.HasBaseType<Document>();
builder.Property(x => x.Code).IsRequired().HasMaxLength(500);
}
}
public class CategoryDocumentMapping : IEntityTypeConfiguration<CategoryDocument>
{
public void Configure(EntityTypeBuilder<CategoryDocument> builder)
{
builder.Property<long>("Id").ValueGeneratedOnAdd();
builder.ToTable("Categories");
// builder.HasBaseType<Document>();
builder.HasMany<Document>(x => x.Documents).WithOne().HasForeignKey("CategoryId");
}
}
result of mapping is this:
Tables and relations in database is as I expect. But when I try to fetch a person from database, desired person get fetched but its documents don't.

How to separate entity's primary key from base class?

I want to accomplish clean architecture with EF Core.
I trid to separate primary key from base class, but it failed with below exception.
System.InvalidOperationException: 'A key cannot be configured on 'ManagerEntity' because it is a derived type. The key must be configured on the root type 'Manager'. If you did not intend for 'Manager' to be included in the model, ensure that it is not referenced by a DbSet property on your context, referenced in a configuration call to ModelBuilder, or referenced from a navigation on a type that is included in the model.'
Core
public class Manager
{
public Manager(Guid identifier, string email)
{
Identifier = identifier;
Email = email;
}
public Guid Identifier { get; }
public string Email { get; }
public void FixPrinter(Printer printer)
{
printer.IsOutOfControl = true;
}
}
public class Printer
{
public Printer(Guid token)
{
Token = token;
Manager = null;
IsOutOfControl = false;
}
public Guid Token { get; }
public Manager? Manager { get; set; }
public bool IsOutOfControl { get; set; }
}
Infrastructure
public class ApplicationContext
: DbContext
{
// ...
public DbSet<ManagerEntity> ManagerSet { get; set; }
public DbSet<PrinterEntity> PrinterSet { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new ManagerEntityConfiguration(Database));
modelBuilder.ApplyConfiguration(new PrinterEntityConfiguration(Database));
}
}
Configure Manager
public sealed class ManagerEntity
: Manager
{
public ManagerEntity(string email)
: base(Guid.Empty, email)
{
}
// Primary key for database.
public long Id { get; }
}
internal sealed class ManagerEntityConfiguration
: IEntityTypeConfiguration<ManagerEntity>
{
private readonly DatabaseFacade _database;
public ManagerEntityConfiguration(DatabaseFacade database)
{
_database = database;
}
public void Configure(EntityTypeBuilder<ManagerEntity> builder)
{
builder
.Property(e => e.Id)
.ValueGeneratedOnAdd();
// The exception occurs here.
builder
.HasKey(e => e.Id);
// ...
}
}
Configure Printer
public sealed class PrinterEntity
: Printer
{
public PrinterEntity()
: base(Guid.Empty)
{
}
// Primary key for database.
public long Id { get; }
}
internal sealed class PrinterEntityConfiguration
: IEntityTypeConfiguration<PrinterEntity>
{
private readonly DatabaseFacade _database;
public PrinterEntityConfiguration(DatabaseFacade database)
{
_database = database;
}
public void Configure(EntityTypeBuilder<PrinterEntity> builder)
{
builder
.Property(e => e.Id)
.ValueGeneratedOnAdd();
builder
.HasKey(e => e.Id);
// ...
}
}
Web API
app.MapPost("/printer", async (ApplicationContext context) =>
{
PrinterEntity printer = new()
{
Manager = new ManagerEntity("master#google.com"),
};
await context.PrinterSet.AddAsync(printer);
await context.SaveChangesAsync();
return printer;
});
Should I architect it by using interface, not inheritance?
At Core,
public interface IPrinter
{
public Manager? Manager { get; set; }
}
At Infrastructure,
public sealed class PrinterEntity : IPrinter
{
// ...
}
Github source code
Thanks, #roji!
https://github.com/dotnet/efcore/issues/27421#issuecomment-1034762908
This is likely happening because you're mapping both Manager and ManagerEntity in your model, which means you're configuring inheritance mapping (i.e. EF thinks you intend to store both Manager and ManagerEntity instances in the database). With inheritance mapping, the key must be specified at the root.
However, it seems like you only want the class separation on the .NET side, without needing any actual hierarchy, so make sure you are not mapping the base class (Manager in the above). See the minimal code sample below.
await using var ctx = new BlogContext();
await ctx.Database.EnsureDeletedAsync();
await ctx.Database.EnsureCreatedAsync();
public class BlogContext : DbContext
{
// Uncomment the below to make the exception appear
// public DbSet<Manager> Managers { get; set; }
public DbSet<ManagerEntity> ManagerEntities { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer(#"Server=localhost;Database=test;User=SA;Password=Abcd5678;Connect Timeout=60;ConnectRetryCount=0")
.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ManagerEntity>().HasKey(b => b.Id);
}
}
public class Manager
{
public string Name { get; set; }
}
public class ManagerEntity : Manager
{
public int Id { get; set; }
}
However... if the goal is simply not to expose an Id property on Manager, there are simpler ways to do that rather than introducing a .NET hierarchy. You can have a private _id field instead, which would be used by EF Core but not otherwise exposed in your application, keeping your data model clean (see docs). Alternatively, you can have an Id shadow property, removing the field/property from your CLR type altogether.

Proper way to use two contexts in asp.net core with Entity Framework Core

I am trying to apply what I have been studying and I have been involved in a part of contexts.
What I'm trying to apply is a Microsoft.Identity structure along with the application entities.
Structure of the solution:
BaseFull.Web
BaseFull.Entities
BaseFull.Infra.Data
I have created an interface to keep the same new properties and so do not have problem of migration of two classes to the same 'table'
public interface IUsuario
{
string Fullname { get; set; }
}
I applied to my User entity and ApplicationUser:
namespace BaseFull.Web.Models
{
// Add profile data for application users by adding properties to the ApplicationUser class
public class ApplicationUser : IdentityUser, IUsuario
{
public string Fullname { get; set; }
}
}
namespace BaseFull.Entities.Models
{
public class Usuario : IUsuario
{
public string Fullname { get; set; }
public Usuario()
{
Id = Guid.NewGuid().ToString();
}
#region Propriedades da AspnetUsers(Identity)
public string Id { get; set; }
public virtual string Email { get; set; }
public virtual string UserName { get; set; }
...
#endregion
}
I've separated the entities in a project. I am also trying to separate the context in a project and it is at this moment that I am in difficulties.
namespace BaseFull.Infra.Data.Context
{
public class BaseFullContext: DbContext
{
public DbSet<Usuario> Usuarios { get; set; }
....
namespace BaseFull.Web.Data
{
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<ApplicationUser>().ToTable("Usuarios");
In the Startup class of the Web project I made the call of the contexts:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddDbContext<BaseFullContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));....
I had an assembly error, which I resolved by changing the service to the same assembly as the main project
services.AddDbContext<BaseFullAppContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")
, b => b.MigrationsAssembly("BaseFull.Web"))
However, running the second context update generated the error 'table already exists'.
There is already an object named 'Usuarios' in the database.
My biggest question is how to solve this problem, which is the most correct and efficient way to use both contexts for this.
The User configuration class to remain the same as ApplicationUser did not show here because I do not think it's necessary, but if I need to, I'll show it as well.

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