Giter VIP home page Giter VIP logo

Comments (10)

bytefish avatar bytefish commented on September 26, 2024

Can you make up an example of the problem? Pseudocode is totally OK. At the moment I assume you do explicit Migrations. The Migrations should go into a common project, that's shared between all projects. So if you try to create a table with the same Table Name for example, the Migration should fail and as thus the mapping can't be broken this way.

from bytefish.de.

MBroertjes avatar MBroertjes commented on September 26, 2024

Currently I'm not using your ModularEfCore implementation but an in-house produced similiar setup.
I ran into a problem regarding mapping and the explicit migration created.
In the main project there's an entity, which mapping is handled there, and in a module I have another entity that needs to have a relation to that entity.

So the setup is as follows. In the main project

namespace MainProject.Data.Models
{
     public class MainEntity
     {
          public int Id { get; set; }
          public string Name { get; set; }
          public string PropertyNotUsedInModule { get; set; }

          public MyModule.Data.Models.MainEntity MyModuleMainEntity { get; set; }
     }
}

// relevant mapping:
modelBuilder.Entity<MainEntity>(entity =>
{
     // one to one relation to MyModule MainEntity
     entity.HasOne(e => e.MyModuleMainEntity)
          .WithOne()
          .HasForeignKey<MyModule.Data.Models.MainEntity>(e => e.Id);
     });

And in the module:

namespace MyModule.Data.Models
{
     public class ModuleEntity
     {
          public int Id { get; set; }
          public string Name { get; set; }
          public int MainEntityId { get; set; }
          public MainEntity MainEntity { get; set; }
     }
     
     public class MainEntity
     {
          public int Id { get; set; }
          public string Name { get; set; }
          public ICollection<ModuleEntity> ModuleEntities { get; set; }
     }
}

When adding a migration all goes well except for the column 'Name' that is used in both the main project and the module.
Next to the column 'Name' being created in the table, there is also a column 'MainEntity_Name' being created.

I was hoping that the setup used by you has something in place for this 'sharing' of entities between modules and between the main project and a module.

from bytefish.de.

bytefish avatar bytefish commented on September 26, 2024

What about making sure each entity maps to its own table? You didn't define the table name and your classes have the same name. My guess is, that Entity Framework Core by Convention uses the Class Name as Table Name. Did you try to Map them to different tables?

from bytefish.de.

bytefish avatar bytefish commented on September 26, 2024

How to prevent this? Abstract the mapping like I did in my post, then make sure the constructor takes a Schema name and a Table name. At one point iterate over the mappings and throw an Exception if you find two mappings the same names.

from bytefish.de.

MBroertjes avatar MBroertjes commented on September 26, 2024

Let me first express my gratitude and appreciation for taking the time to respond to my questions. Thank you!

I left out some of the mapping details for brevity. The 'MainEntity' classes both are mapped to the same database table, that is correct. The one-on-one relationship between the two ensures the primary key is shared and stored in the same column. But apparently EF migrations doesn't seem able to map the identical 'Name' properties to the same column in the database.

Perhaps it's better to move away from my example and my code, since I'm not using ModularEfCore (yet ;-)). How about the following example.

A customer relationship management module (CRM) and a shipment module both have an 'Customer' entity, which originates in the core module.
The 'Customer' in the core module has basic properties to support loggin in to the applicatie, like a username, password and first/last name.
The 'Customer' in the CRM module has additional properties for contact like phone number and e-mailaddress, as well as a log of correspondence with the customer which is linked to the 'Customer' entity.
The 'Customer' in the shipment module has additional properties for shipment like address and billing information, as well as a history of orders and shipments of the customer.

How is this handled in ModularEfCore? Partial classes aren't an option since the modules define their classes in their own assemblies.
Where are the extra poperties put and where is the mapping located? The migrations are probably created in the main (core) project.
There's no need to create a working example if that takes more time then just globally indicating where things are put and a description of how things work.

from bytefish.de.

bytefish avatar bytefish commented on September 26, 2024

@MBroertjes No worries.

This is also an interesting topic for me, because I experiment with the best architecture for mid- / large-scale monolithic projects. What I always do is to separate the Domain Model and the Mappings completely. I never use Attributes, but do everything in a separate class handling the mapping.

What I have learnt through many projects is, that it's never good to add too much magic to a system.

Let's say you have a class BaseEntity:

public abstract class BaseEntity {

    public int Id { get; set; }

    public DateTime CreatedOn { get; set; }

    public string CreatedBy { get; set; }

}

Your other C# classes derive from this BaseEntity, because they should all be audited.

And from this point on: Don't overthink it.

In all honesty: I always rewrite the complete database mapping for all these classes from zero. Because maybe you want a Primary Key of one table to have a special name? Maybe a Primary Key of one table should be auto incremented, but the other shouldn't? Maybe I need a Composite Primary Key and my abstraction breaks there? And so on, and so on. Maybe you find your own edge cases.

I am pretty sure you can come up with a base class BaseEntityMapping, that does these general mappings on the EntityTypeBuilder<TEntityType> for you... like this:

    public abstract class BaseEntityMap<TEntityType>
        where TEntityType : BaseEntity
    {
        public void Map(ModelBuilder builder)
        {
            var builder = builder.Entity<TEntityType>();
            
            // Apply the General Mappings:
            builder
                .HasKey(x => x.Id);

            builder.Property(x => x.Id)
                .HasColumnName("Id")
                .ValueGeneratedOnAdd();

            builder
                .Property(x => x.CreatedOn)
                .HasColumnName("CreatedOn");

            builder
                .Property(x => x.CreatedBy)
                .HasColumnName("CreatedBy");
            
            // Apply the Module-specific mappings:
            InternalMap(builder);
        }

        protected abstract void InternalMap(EntityTypeBuilder<TEntityType> builder);
    }

Now all your other BaseEntity implementations would need to implement the above BaseEntityMapping for the mapping, which maps the general properties and all additional properties would be mapped by the module-specific implementations.

But like I said... I would just rewrite the above for all entities. Because if you tackle the problem with such a simple approach, there won't be a problem for EntityFramework Core to generate the migrations. All this comes at the cost of duplication. But duplication is better, than the wrong abstraction.

from bytefish.de.

bytefish avatar bytefish commented on September 26, 2024

I am closing the issue. But feel free to discuss the issue further. 😎

from bytefish.de.

MBroertjes avatar MBroertjes commented on September 26, 2024

I see the elegance of the code in your reply, but it doesn't add anything meaningfull of on the topic of shared entities in different assemblies.
That is what 'ModularEfCore' is designed for, isn't it?

from bytefish.de.

bytefish avatar bytefish commented on September 26, 2024

Hm, then I don't understand your use case correctly, sorry. 🤔

from bytefish.de.

MBroertjes avatar MBroertjes commented on September 26, 2024

Okay, thanks anyway.

from bytefish.de.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.