EF Core Mapping EntityTypeConfiguration


129

Di EF6 kami biasanya dapat menggunakan cara ini untuk mengkonfigurasi Entitas.

public class AccountMap : EntityTypeConfiguration<Account>
{
    public AccountMap()
    {
        ToTable("Account");
        HasKey(a => a.Id);

        Property(a => a.Username).HasMaxLength(50);
        Property(a => a.Email).HasMaxLength(255);
        Property(a => a.Name).HasMaxLength(255);
    }
}

Bagaimana kita bisa melakukannya di EF Core, sejak saat kelas I Mewarisi EntityTypeConfiguration yang tidak dapat menemukan kelas tersebut.

Saya mengunduh kode sumber mentah EF Core dari GitHub, saya tidak dapat menemukannya. Bisakah seseorang membantu dalam hal ini?


8
Mengapa tidak menerima jawaban itu?
Den

karena di beta5 sekarang, ketika kita menempatkan maxLength (50). di db itu menghasilkan nvarchar (max)
Herman

6
Bagi siapa pun yang tertarik dengan ini, sekarang IEntityTypeConfiguration<T>ada void Configure()metode dengan satu yang dapat Anda terapkan. Rincian di sini: github.com/aspnet/EntityFramework/pull/6989
Galilyou

Jawaban:


183

Sejak EF Core 2.0 ada IEntityTypeConfiguration<TEntity>. Anda bisa menggunakannya seperti ini:

class CustomerConfiguration : IEntityTypeConfiguration<Customer>
{
  public void Configure(EntityTypeBuilder<Customer> builder)
  {
     builder.HasKey(c => c.AlternateKey);
     builder.Property(c => c.Name).HasMaxLength(200);
   }
}

...
// OnModelCreating
builder.ApplyConfiguration(new CustomerConfiguration());

Lebih lanjut tentang ini dan fitur baru lainnya yang diperkenalkan di 2.0 dapat ditemukan di sini .


8
Ini adalah jawaban terbaik untuk EF Core 2.0. Terima kasih!
Collin M. Barrett

2
Ini luar biasa. Saya sedang mencari cara untuk memisahkan definisi API yang lancar. Terima kasih
Blaze

Lihat juga jawaban ini untuk "ToTable" dan "HasColumnName", dll ::: stackoverflow.com/questions/43200184/…
granadaCoder

jika Anda memiliki konfigurasi kustom man, taruh saja builder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);itu akan menerapkan semua konfigurasi kustom
alim91

52

Anda dapat mencapai ini melalui beberapa jenis tambahan sederhana:

internal static class ModelBuilderExtensions
{
   public static void AddConfiguration<TEntity>(
     this ModelBuilder modelBuilder, 
     DbEntityConfiguration<TEntity> entityConfiguration) where TEntity : class
   {     
       modelBuilder.Entity<TEntity>(entityConfiguration.Configure);
   }
}

internal abstract class DbEntityConfiguration<TEntity> where TEntity : class
{     
    public abstract void Configure(EntityTypeBuilder<TEntity> entity);
}

Pemakaian:

internal class UserConfiguration : DbEntityConfiguration<UserDto>
{
    public override void Configure(EntityTypeBuilder<UserDto> entity)
    {
        entity.ToTable("User");
        entity.HasKey(c => c.Id);
        entity.Property(c => c.Username).HasMaxLength(255).IsRequired();
        // etc.
    }
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.AddConfiguration(new UserConfiguration());
}

1
Dimana ForSqlServerToTable()?
im1dermike


1
Bagaimana cara menggunakan HasColumnType dengan ini? . Misalnya. entity.Property(c => c.JoinDate).HasColumnType("date");
Biju Soman

OnModelCreatingtelah diperbarui untuk membutuhkan DbModelBuilder. Cara untuk menambahkan konfigurasi ke ini sekarangmodelBuilder.Configurations.Add(new UserConfiguration());
Izzy

2
@Izzy - DbModelBuilder adalah Entity Framework 6.0, ModelBuilder adalah EF Core. Mereka adalah rakitan yang berbeda dan dalam hal ini pertanyaannya khusus untuk EF Core.
Jason

29

Di EF7, Anda mengganti OnModelCreating pada kelas DbContext yang Anda implementasikan.

protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Account>()
            .ForRelational(builder => builder.Table("Account"))
            .Property(value => value.Username).MaxLength(50)
            .Property(value => value.Email).MaxLength(255)
            .Property(value => value.Name).MaxLength(255);
    }

23
Jadi, jika saya memiliki 20 konfigurasi tipe entitas, saya menempatkannya dalam satu metode besar?
Den

6
Secara default, sepertinya begitu. Anda dapat membuat kelas FooMapper / FooModelBuilder Anda sendiri yang memperluas kelas dasar dan memiliki metode yang Anda berikan kepada EntityBuilder <Foo> yang diketik. Anda bahkan dapat menggunakan injeksi ketergantungan baru dan antarmuka IConfiguration agar mereka menemukan / memanggil Anda secara otomatis, jika Anda ingin menjadi mewah!
Avi Cherry

1
Sama-sama. Memberi suara untuk jawaban (dan mendorong penanya untuk menerimanya) bahkan lebih baik!
Avi Cherry

Saya biasanya melakukan ini :)
Den

4
Coba alat injeksi ketergantungan baru? Buat IEntityMapperStrategyantarmuka dengan void MapEntity(ModelBuilder, Type)tanda tangan dan bool IsFor(Type). Implementasikan antarmuka sebanyak atau sesedikit yang Anda inginkan (sehingga Anda dapat membuat kelas yang dapat memetakan lebih dari satu entitas jika Anda mau) dan kemudian membuat kelas lain (penyedia strategi) yang memasukkan salah satu IEnumerabledari semua IEntityMapperStrategies. Lihat di sini di bawah 'Jenis Khusus'. Masukkan itu ke dalam konteks Anda.
Avi Cherry

22

Ini menggunakan yang terbaru, beta 8. Coba ini:

public class AccountMap
{
    public AccountMap(EntityTypeBuilder<Account> entityBuilder)
    {
        entityBuilder.HasKey(x => x.AccountId);

        entityBuilder.Property(x => x.AccountId).IsRequired();
        entityBuilder.Property(x => x.Username).IsRequired().HasMaxLength(50);
    }
}

Kemudian di DbContext Anda:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        new AccountMap(modelBuilder.Entity<Account>());
    }

3
Saya akhirnya melakukan hal serupa dengan ini. Saya memutuskan untuk menggunakan metode statis daripada konstruktor.
Matt Sanders

Saya menggunakan metodologi ini dan sampai saat ini saya tidak memiliki masalah kecuali dengan warisan. Jika saya ingin mewarisi AccountMap dalam contoh Anda ke yang baru dan menambahkan kunci alternatif - apa pendekatan terbaik?
chris

14

Anda dapat menggunakan refleksi untuk melakukan hal-hal yang sangat mirip dengan cara kerjanya di EF6, dengan kelas pemetaan terpisah untuk setiap entitas. Ini bekerja di final RC1:

Pertama, buat antarmuka untuk jenis pemetaan Anda:

public interface IEntityTypeConfiguration<TEntityType> where TEntityType : class
{
    void Map(EntityTypeBuilder<TEntityType> builder);
}

Kemudian buat kelas pemetaan untuk setiap entitas Anda, misalnya untuk Personkelas:

public class PersonMap : IEntityTypeConfiguration<Person>
{
    public void Map(EntityTypeBuilder<Person> builder)
    {
        builder.HasKey(x => x.Id);
        builder.Property(x => x.Name).IsRequired().HasMaxLength(100);
    }
}

Sekarang, keajaiban refleksi OnModelCreatingdalam DbContextimplementasi Anda :

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);

    // Interface that all of our Entity maps implement
    var mappingInterface = typeof(IEntityTypeConfiguration<>);

    // Types that do entity mapping
    var mappingTypes = typeof(DataContext).GetTypeInfo().Assembly.GetTypes()
        .Where(x => x.GetInterfaces().Any(y => y.GetTypeInfo().IsGenericType && y.GetGenericTypeDefinition() == mappingInterface));

    // Get the generic Entity method of the ModelBuilder type
    var entityMethod = typeof(ModelBuilder).GetMethods()
        .Single(x => x.Name == "Entity" && 
                x.IsGenericMethod && 
                x.ReturnType.Name == "EntityTypeBuilder`1");

    foreach (var mappingType in mappingTypes)
    {
        // Get the type of entity to be mapped
        var genericTypeArg = mappingType.GetInterfaces().Single().GenericTypeArguments.Single();

        // Get the method builder.Entity<TEntity>
        var genericEntityMethod = entityMethod.MakeGenericMethod(genericTypeArg);

        // Invoke builder.Entity<TEntity> to get a builder for the entity to be mapped
        var entityBuilder = genericEntityMethod.Invoke(builder, null);

        // Create the mapping type and do the mapping
        var mapper = Activator.CreateInstance(mappingType);
        mapper.GetType().GetMethod("Map").Invoke(mapper, new[] { entityBuilder });
    }
}

Referensi apa yang digunakan DataContextdan .Wheredigunakan? Saya membuat proyek terpisah untuk ini dan sepertinya tidak menemukan referensi.
Ruchan

.Whereadalah System.Linq, DataContextadalah kelas tempat kode ditambahkan ( DbContextimpl EF saya )
Cocowalla

12

Sejak EF Core 2.2 Anda dapat menambahkan semua konfigurasi (kelas, yang menerapkan antarmuka IEntityTypeConfiguration) dalam satu baris dalam metode OnModelCreating di kelas, yang diwarisi dari kelas DbContext

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    //this will apply configs from separate classes which implemented IEntityTypeConfiguration<T>
    modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
}

Dan, seperti yang disebutkan dalam jawaban sebelumnya, sejak EF Core 2.0, Anda dapat mengimplementasikan antarmuka IEntityTypeConfiguration, mengatur konfigurasi pemetaan dengan menggunakan FluentAPI dalam metode Configure.

public class QuestionAnswerConfig : IEntityTypeConfiguration<QuestionAnswer>
{
    public void Configure(EntityTypeBuilder<QuestionAnswer> builder)
    {
      builder
        .HasKey(bc => new { bc.QuestionId, bc.AnswerId });
      builder
        .HasOne(bc => bc.Question)
        .WithMany(b => b.QuestionAnswers)
        .HasForeignKey(bc => bc.QuestionId);
      builder
        .HasOne(bc => bc.Answer)
        .WithMany(c => c.QuestionAnswers)
        .HasForeignKey(bc => bc.AnswerId);
    }
}

6

Inilah yang saya lakukan dalam proyek yang sedang saya kerjakan.

public interface IEntityMappingConfiguration<T> where T : class
{
    void Map(EntityTypeBuilder<T> builder);
}

public static class EntityMappingExtensions
{
     public static ModelBuilder RegisterEntityMapping<TEntity, TMapping>(this ModelBuilder builder) 
        where TMapping : IEntityMappingConfiguration<TEntity> 
        where TEntity : class
    {
        var mapper = (IEntityMappingConfiguration<TEntity>)Activator.CreateInstance(typeof (TMapping));
        mapper.Map(builder.Entity<TEntity>());
        return builder;
    }
}

Pemakaian:

Dalam metode OnModelCreating Konteks Anda:

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder
            .RegisterEntityMapping<Card, CardMapping>()
            .RegisterEntityMapping<User, UserMapping>();
    }

Contoh kelas pemetaan:

public class UserMapping : IEntityMappingConfiguration<User>
{
    public void Map(EntityTypeBuilder<User> builder)
    {
        builder.ToTable("User");
        builder.HasKey(m => m.Id);
        builder.Property(m => m.Id).HasColumnName("UserId");
        builder.Property(m => m.FirstName).IsRequired().HasMaxLength(64);
        builder.Property(m => m.LastName).IsRequired().HasMaxLength(64);
        builder.Property(m => m.DateOfBirth);
        builder.Property(m => m.MobileNumber).IsRequired(false);
    }
}

Satu hal lain yang ingin saya lakukan untuk memanfaatkan perilaku pelipatan Visual Studio 2015 adalah untuk Entitas yang disebut 'Pengguna', beri nama file pemetaan Anda 'User.Mapping.cs', Visual Studio akan melipat file di penjelajah solusi sehingga dimuat di bawah file kelas entitas.


Terima kasih atas solusinya. Saya akan mengoptimalkan kode solusi saya di akhir proyek saya ... Saya memastikannya di masa mendatang.
Miroslav Siska

Saya hanya dapat mengasumsikan 'IEntityTypeConfiguration <T>' dan Configure(builder)tidak ada pada tahun 2016? Dengan sedikit perubahan kabel untuk menunjuk ke TypeConfiguration, tidak perlu antarmuka 'ekstra'.
WernerCD

3

Saya mengakhiri dengan solusi ini:

public interface IEntityMappingConfiguration
{
    void Map(ModelBuilder b);
}

public interface IEntityMappingConfiguration<T> : IEntityMappingConfiguration where T : class
{
    void Map(EntityTypeBuilder<T> builder);
}

public abstract class EntityMappingConfiguration<T> : IEntityMappingConfiguration<T> where T : class
{
    public abstract void Map(EntityTypeBuilder<T> b);

    public void Map(ModelBuilder b)
    {
        Map(b.Entity<T>());
    }
}

public static class ModelBuilderExtenions
{
    private static IEnumerable<Type> GetMappingTypes(this Assembly assembly, Type mappingInterface)
    {
        return assembly.GetTypes().Where(x => !x.IsAbstract && x.GetInterfaces().Any(y => y.GetTypeInfo().IsGenericType && y.GetGenericTypeDefinition() == mappingInterface));
    }

    public static void AddEntityConfigurationsFromAssembly(this ModelBuilder modelBuilder, Assembly assembly)
    {
        var mappingTypes = assembly.GetMappingTypes(typeof (IEntityMappingConfiguration<>));
        foreach (var config in mappingTypes.Select(Activator.CreateInstance).Cast<IEntityMappingConfiguration>())
        {
            config.Map(modelBuilder);
        }
    }
}

Penggunaan Sampel:

public abstract class PersonConfiguration : EntityMappingConfiguration<Person>
{
    public override void Map(EntityTypeBuilder<Person> b)
    {
        b.ToTable("Person", "HumanResources")
            .HasKey(p => p.PersonID);

        b.Property(p => p.FirstName).HasMaxLength(50).IsRequired();
        b.Property(p => p.MiddleName).HasMaxLength(50);
        b.Property(p => p.LastName).HasMaxLength(50).IsRequired();
    }
}

dan

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.AddEntityConfigurationsFromAssembly(GetType().Assembly);
}

Saya mendapatkan kesalahan waktu kompilasi: " Operator '! X.IsAbstract' tidak dapat diterapkan ke operan jenis 'grup metode' " di '! X.IsAbstract' (System.Type.IsAbstract) di ModelBuilderExtenions.GetMappingTypes () . Apakah saya perlu menambahkan referensi ke mscorlib? Bagaimana cara melakukannya pada proyek .NET Core 1.0?
RandyDaddis

untuk proyek inti .net (menggunakan netstandard), Anda perlu menggunakan ekstensi GetTypeInfo () di namespace System.Reflection. Digunakan sebagai x.GetTypeInfo () IsAbstract atau x.GetTypeInfo () GetInterfaces ()..
animalito maquina

Saya telah menggunakan sebagian dari solusi Anda pada solusi saya dan itu bekerja dengan baik. Terima kasih!
Diego Cotini

2

Cukup terapkan IEntityTypeConfiguration

public abstract class EntityTypeConfiguration<TEntity> : IEntityTypeConfiguration<TEntity> where TEntity : class
{
    public abstract void Configure(EntityTypeBuilder<TEntity> builder);
}

dan kemudian menambahkannya ke konteks entitas Anda

public class ProductContext : DbContext, IDbContext
{
    public ProductContext(DbContextOptions<ProductContext> options)
        : base((DbContextOptions)options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.ApplyConfiguration(new ProductMap());
    }

    public DbSet<Entities.Product> Products { get; set; }
}


1

Dalam Entity Framework Core 2.0:

Saya mengambil jawaban Cocowalla dan mengadaptasinya untuk v2.0:

    public static class ModelBuilderExtenions
    {
        private static IEnumerable<Type> GetMappingTypes(this Assembly assembly, Type mappingInterface)
        {
            return assembly.GetTypes().Where(x => !x.IsAbstract && x.GetInterfaces().Any(y => y.GetTypeInfo().IsGenericType && y.GetGenericTypeDefinition() == mappingInterface));
        }

        public static void AddEntityConfigurationsFromAssembly(this ModelBuilder modelBuilder, Assembly assembly)
        {
            // Types that do entity mapping
            var mappingTypes = assembly.GetMappingTypes(typeof(IEntityTypeConfiguration<>));

            // Get the generic Entity method of the ModelBuilder type
            var entityMethod = typeof(ModelBuilder).GetMethods()
                .Single(x => x.Name == "Entity" &&
                        x.IsGenericMethod &&
                        x.ReturnType.Name == "EntityTypeBuilder`1");

            foreach (var mappingType in mappingTypes)
            {
                // Get the type of entity to be mapped
                var genericTypeArg = mappingType.GetInterfaces().Single().GenericTypeArguments.Single();

                // Get the method builder.Entity<TEntity>
                var genericEntityMethod = entityMethod.MakeGenericMethod(genericTypeArg);

                // Invoke builder.Entity<TEntity> to get a builder for the entity to be mapped
                var entityBuilder = genericEntityMethod.Invoke(modelBuilder, null);

                // Create the mapping type and do the mapping
                var mapper = Activator.CreateInstance(mappingType);
                mapper.GetType().GetMethod("Configure").Invoke(mapper, new[] { entityBuilder });
            }
        }


    }

Dan itu digunakan dalam DbContext seperti ini:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.AddEntityConfigurationsFromAssembly(GetType().Assembly);
    }

Dan inilah cara Anda membuat konfigurasi tipe entitas untuk entitas:

    public class UserUserRoleEntityTypeConfiguration : IEntityTypeConfiguration<UserUserRole>
    {
        public void Configure(EntityTypeBuilder<UserUserRole> builder)
        {
            builder.ToTable("UserUserRole");
            // compound PK
            builder.HasKey(p => new { p.UserId, p.UserRoleId });
        }
    }

Tidak berhasil untuk saya. Pengecualian:Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true.
Tohid

PS: Menemukan solusinya: &&! T.IsGenericType. Karena saya memiliki kelas dasar yang bersifat generik ( class EntityTypeConfigurationBase<TEntity> : IEntityTypeConfiguration<TEntity>). Anda tidak dapat membuat instance dari kelas dasar ini.
Tohid

0

Apakah saya benar?

public class SmartModelBuilder<T> where T : class         {

    private ModelBuilder _builder { get; set; }
    private Action<EntityTypeBuilder<T>> _entityAction { get; set; }

    public SmartModelBuilder(ModelBuilder builder, Action<EntityTypeBuilder<T>> entityAction)
    {
        this._builder = builder;
        this._entityAction = entityAction;

        this._builder.Entity<T>(_entityAction);
    }
}   

I can Pass config:

 protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        // Customize the ASP.NET Identity model and override the defaults if needed.
        // For example, you can rename the ASP.NET Identity table names and more.
        // Add your customizations after calling base.OnModelCreating(builder);



        new SmartModelBuilder<Blog>(builder, entity => entity.Property(b => b.Url).Required());

    } 

Jawaban yang diterima tampaknya lebih baik dari ini. Keduanya memiliki efek samping negatif yang sama karena OnModelCreating () yang sangat berantakan, tetapi jawaban yang diterima tidak memerlukan kelas pembantu apa pun. Apakah ada sesuatu yang saya lewatkan sehingga jawaban Anda membaik?
Berlayar Judo

0

Saya mengikuti pendekatan serupa dengan cara Microsoft mengimplementasikan ForSqlServerToTable

menggunakan metode ekstensi ...

yang parsial bendera diperlukan jika Anda ingin menggunakan nama kelas yang sama di beberapa file

public class ConsignorUser
{
    public int ConsignorId { get; set; }

    public string UserId { get; set; }

    public virtual Consignor Consignor { get; set; }
    public virtual User User { get; set; }

}

public static partial class Entity_FluentMappings
{
    public static EntityTypeBuilder<ConsignorUser> AddFluentMapping<TEntity> (
        this EntityTypeBuilder<ConsignorUser> entityTypeBuilder) 
        where TEntity : ConsignorUser
    {
       entityTypeBuilder.HasKey(x => new { x.ConsignorId, x.UserId });
       return entityTypeBuilder;
    }      
}

Kemudian di DataContext OnModelCreating lakukan panggilan Anda untuk setiap ekstensi ...

 public class DataContext : IdentityDbContext<User>
{

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        // Customize the ASP.NET Identity model and override the defaults if needed.
        // For example, you can rename the ASP.NET Identity table names and more.
        // Add your customizations after calling base.OnModelCreating(builder);

        builder.Entity<ConsignorUser>().AddFluentMapping<ConsignorUser>();
        builder.Entity<DealerUser>().AddFluentMapping<DealerUser>();           

    }

Dengan cara ini kami mengikuti pola yang sama yang digunakan oleh metode pembangun lainnya.

Apa yang kamu pikirkan?



0

Saya memiliki proyek yang memungkinkan Anda untuk mengkonfigurasi entitas di luar DbContext.OnModelCreatingAnda mengkonfigurasi setiap entitas dalam kelas terpisah yang mewarisiStaticDotNet.EntityFrameworkCore.ModelConfiguration.EntityTypeConfiguration

Pertama, Anda perlu membuat kelas yang mewarisi dari StaticDotNet.EntityFrameworkCore.ModelConfiguration.EntityTypeConfiguration<TEntity>mana TEntitykelas yang ingin Anda konfigurasi.

using StaticDotNet.EntityFrameworkCore.ModelConfiguration;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

public class ExampleEntityConfiguration
    : EntityTypeConfiguration<ExampleEntity>
{
    public override void Configure( EntityTypeBuilder<ExampleEntity> builder )
    {
        //Add configuration just like you do in DbContext.OnModelCreating
    }
}

Kemudian di kelas Startup Anda, Anda hanya perlu memberi tahu Entity Framework di mana menemukan semua kelas konfigurasi Anda saat Anda mengonfigurasi DbContext.

using StaticDotNet.EntityFrameworkCore.ModelConfiguration;

public void ConfigureServices(IServiceCollection services)
{
    Assembly[] assemblies = new Assembly[]
    {
        // Add your assembiles here.
    };

    services.AddDbContext<ExampleDbContext>( x => x
        .AddEntityTypeConfigurations( assemblies )
    );
}

Ada juga opsi untuk menambahkan konfigurasi tipe menggunakan penyedia. Repo memiliki dokumentasi lengkap tentang cara menggunakannya.

https://github.com/john-t-white/StaticDotNet.EntityFrameworkCore.ModelConfiguration


Harap jangan memposting jawaban yang sama untuk beberapa pertanyaan. Jika informasi yang sama benar-benar menjawab kedua pertanyaan, maka satu pertanyaan (biasanya yang lebih baru) harus ditutup sebagai duplikat dari yang lain. Anda dapat menunjukkan ini dengan memilih untuk menutupnya sebagai duplikat atau, jika Anda tidak memiliki cukup reputasi untuk itu, angkat bendera untuk menunjukkan bahwa itu duplikat. Jika tidak, pastikan Anda menyesuaikan jawaban Anda dengan pertanyaan ini dan jangan hanya menempelkan jawaban yang sama di banyak tempat.
elixenide
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.