EF Core Mapping EntityTypeConfiguration


129

EF6'da Varlığı yapılandırmak için genellikle bu yolu kullanabiliriz.

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);
    }
}

EF Core'da nasıl yapabiliriz, çünkü sınıf I Inherit EntityTypeConfiguration sınıfını bulamıyor.

EF Core ham kaynak kodunu GitHub'dan indiriyorum, bulamıyorum. Birisi bu konuda yardımcı olabilir mi?


8
Neden bu cevabı kabul etmiyorsun?
Den

beta5'te olduğu için maxLength (50) koyduğumuzda. DB'de nvarchar (maks.) üretir
Herman

6
Bununla ilgilenen diğer herkes için , uygulayabileceğiniz IEntityTypeConfiguration<T>tek bir void Configure()yöntem var. Ayrıntılar burada: github.com/aspnet/EntityFramework/pull/6989
Galilyou

Yanıtlar:


183

EF Core 2.0'dan beri var IEntityTypeConfiguration<TEntity>. Bunu şu şekilde kullanabilirsiniz:

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());

Bu ve 2.0'da tanıtılan diğer yeni özellikler hakkında daha fazla bilgiyi burada bulabilirsiniz .


8
Bu, EF Core 2.0 için en iyi yanıttır. Teşekkürler!
Collin M. Barrett

2
Bu mükemmel. Akıcı API tanımlarını ayırmanın bir yolunu arıyordum. Teşekkürler
Blaze

Ayrıca "ToTable" ve "HasColumnName" için bu yanıta bakın, vb ::: stackoverflow.com/questions/43200184/…
granadaCoder

adam özel yapılandırmanız varsa, builder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);tüm özel onayları uygulayacaktır
alim91

52

Bunu bazı basit ek türlerle elde edebilirsiniz:

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);
}

Kullanımı:

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
Nerede ForSqlServerToTable()?
im1dermike


1
Bununla HasColumnType nasıl kullanılır ? . Örneğin. entity.Property(c => c.JoinDate).HasColumnType("date");
Biju Soman

OnModelCreatingbir DbModelBuilder. Buna konfigürasyon eklemenin yolu şimdimodelBuilder.Configurations.Add(new UserConfiguration());
Izzy

2
@Izzy - DbModelBuilder Entity Framework 6.0, ModelBuilder ise EF Core'tur. Farklı derlemelerdir ve bu durumda soru EF Core'a özgüdür.
Jason

29

EF7'de, uyguladığınız DbContext sınıfında OnModelCreating'i geçersiz kılarsınız.

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
Yani 20 varlık türü yapılandırmam varsa, onları büyük bir yönteme koyar mıyım?
Den

6
Varsayılan olarak öyle görünüyor. Bir temel sınıfı genişleten ve yazılı bir EntityBuilder <Foo> ilettiğiniz bir yöntemi olan kendi FooMapper / FooModelBuilder sınıflarınızı oluşturabilirsiniz. Süslü olmak istiyorsanız, yeni bağımlılık enjeksiyonunu ve IConfiguration arayüzünü otomatik olarak keşfetmelerini / çağırmalarını sağlamak için bile kullanabilirsiniz!
Avi Cherry

1
Rica ederim. Bir yanıta yukarı oy vermek (ve soruyu soranı kabul etmesi için cesaretlendirmek) daha da iyidir!
Avi Cherry

Bunu genellikle yaparım :)
Den

4
Yeni bağımlılık ekleme araçlarını deneyin mi? İmzalı bir IEntityMapperStrategyarayüz oluşturun void MapEntity(ModelBuilder, Type)ve bool IsFor(Type). Arabirimi istediğiniz kadar çok veya az kez uygulayın (böylece isterseniz birden fazla varlığı eşleyebilen sınıflar oluşturabilirsiniz) ve ardından IEnumerabletüm IEntityMapperStrategies. "Özel Tipler" altında buraya bakın . Bunu içeriğinize ekleyin.
Avi Cherry

22

Bu, en son beta 8'i kullanıyor. Şunu deneyin:

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);
    }
}

Sonra DbContext'inizde:

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

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

3
Ben de buna benzer bir şey yaptım. Yine de kurucu yerine statik bir yöntem kullanmaya karar verdim.
Matt Sanders

Bu metodolojiyi kullanıyorum ve bu noktaya kadar kalıtım dışında hiçbir sorun yaşamadım. Örneğinizdeki Hesap Haritasını yenisine devralmak ve alternatif bir anahtar eklemek istersem - en iyi yaklaşım ne olur?
chris

14

Her varlık için ayrı bir eşleme sınıfıyla, şeyleri EF6'da nasıl çalıştıklarına çok benzer şekilde yapmak için yansımayı kullanabilirsiniz. Bu RC1 finalinde geçerli:

İlk olarak, eşleme türleriniz için bir arayüz oluşturun:

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

Ardından, varlıklarınızın her biri için bir eşleme sınıfı oluşturun, örneğin bir Personsınıf için:

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

Şimdi, içeri yansıma sihirli OnModelCreatingsizin içinde DbContextuygulanması:

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 });
    }
}

Ne referans yapar DataContextve .Wherekullanımı? Bunun için ayrı bir proje yaptım ve referans bulamadım.
Ruchan

.Whereolduğu System.Linq, DataContextkodu (benim EF eklenir sınıf edilir DbContextimpl)
Cocowalla

12

EF Core 2.2'den beri, tüm yapılandırmaları (IEntityTypeConfiguration arabirimini uygulayan sınıflar), DbContext sınıfından miras alınan sınıfta OnModelCreating yönteminde tek satıra ekleyebilirsiniz.

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

Ve önceki yanıtta belirtildiği gibi, EF Core 2.0'dan beri, Configure yönteminde FluentAPI kullanarak IEntityTypeConfiguration arabirimini, kurulum eşleme yapılandırmasını uygulayabilirsiniz.

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

Şu anda üzerinde çalıştığım bir projede yaptığım şey bu.

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;
    }
}

Kullanımı:

Context'in OnModelCreating yönteminde:

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

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

Örnek eşleme sınıfı:

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);
    }
}

Visual Studio 2015'in katlama davranışından yararlanmak için yapmaktan hoşlandığım bir diğer şey, 'Kullanıcı' adlı bir Varlık için, eşleme dosyanızı 'User.Mapping.cs' olarak adlandırın, Visual Studio dosyayı çözüm gezgininde katlar böylece varlık sınıfı dosyası altında yer alır.


Çözümünüz için teşekkürler. Çözüm kodumu projemin sonunda optimize edeceğim ... Gelecekte emin olacağım.
Miroslav Siska

Yalnızca 'IEntityTypeConfiguration <T>' olduğunu varsayabilirim ve Configure(builder)2016'da yok muyum? TypeConfiguration'a işaret edecek küçük bir kablolama değişikliği ile 'ekstra' arayüze gerek yoktur.
WernerCD

3

Bu çözümle bitirdim:

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);
        }
    }
}

Örnek Kullanım:

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();
    }
}

ve

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

Derleme zamanı hatası alıyorum: " Operator '! X.IsAbstract', ModelBuilderExtenions.GetMappingTypes () 'de'! X.IsAbstract '(System.Type.IsAbstract) ' yöntem grubu ' türündeki işlenenlere uygulanamıyor . Mscorlib'e bir referans eklemem gerekiyor mu? Bunu bir .NET Core 1.0 projesine nasıl yaparım?
RandyDaddis

.net çekirdek projeleri için (netstandard kullanarak) System.Reflection ad alanında GetTypeInfo () uzantısını kullanmanız gerekir. X.GetTypeInfo () olarak kullanın. IsAbstract veya x.GetTypeInfo (). GetInterfaces ()
animalito maquina

Çözümünüzün bir kısmını benimkinde kullandım ve iyi çalıştı. Teşekkürler!
Diego Cotini

2

Sadece IEntityTypeConfiguration'ı uygulayın

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

ve sonra bunu varlık Bağlamınıza ekleyin

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

Entity Framework Core 2.0'da:

Cocowalla'nın cevabını aldım ve v2.0'a uyarladım:

    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 });
            }
        }


    }

Ve DbContext'te şu şekilde kullanılır:

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

Ve bir varlık için bir varlık türü yapılandırmasını şu şekilde oluşturursunuz:

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

Benim için çalışmadı. İstisna:Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true.
Tohid

Not: Çözümü buldum: &&! T.IsGenericType. Çünkü genel olan bir temel sınıfım vardı ( class EntityTypeConfigurationBase<TEntity> : IEntityTypeConfiguration<TEntity>). Bu temel sınıfın bir örneğini oluşturamazsınız.
Tohid

0

Haklı mıyım

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);
    }
}   

Yapılandırmayı geçebilirim:

 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());

    } 

Kabul edilen cevap bundan daha iyi görünüyor. Her ikisi de büyük ölçüde dağınık bir OnModelCreating () 'e sahip olmakla aynı olumsuz yan etkiye sahiptir, ancak kabul edilen yanıt herhangi bir yardımcı sınıf gerektirmez. Cevabınızın iyileştirdiği kaçırdığım bir şey mi var?
Yelken Judo

0

Microsoft'un ForSqlServerToTable'ı uygulama şekline benzer bir yaklaşım izledim

uzantı yöntemi kullanılıyor ...

Kısmi Eğer birden fazla dosya aynı sınıf adını kullanmak istiyorsanız bayrağı gereklidir

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;
    }      
}

Sonra DataContext OnModelCreating'de her bir uzantı için aramanızı yapın ...

 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>();           

    }

Bu şekilde , diğer kurucu yöntemleri tarafından kullanılan aynı kalıbı izliyoruz .

Ne düşünüyorsun?



0

DbContext.OnModelCreatingSiz dışındaki varlıkları yapılandırmanıza izin veren bir projem var, her varlığı ayrı bir sınıfta yapılandırın.StaticDotNet.EntityFrameworkCore.ModelConfiguration.EntityTypeConfiguration

Öncelikle , yapılandırmak istediğiniz sınıfı StaticDotNet.EntityFrameworkCore.ModelConfiguration.EntityTypeConfiguration<TEntity>nereden miras alan bir sınıf oluşturmanız gerekir TEntity.

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
    }
}

Ardından, Başlangıç ​​sınıfınızda, DbContext'inizi yapılandırırken Entity Framework'e tüm yapılandırma sınıflarınızı nerede bulacağını söylemeniz yeterlidir.

using StaticDotNet.EntityFrameworkCore.ModelConfiguration;

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

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

Bir sağlayıcı kullanarak tür yapılandırmaları ekleme seçeneği de vardır. Depo, nasıl kullanılacağına dair eksiksiz belgelere sahiptir.

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


Lütfen aynı cevabı birden fazla soruya göndermeyin. Aynı bilgi her iki soruyu da gerçekten yanıtlıyorsa, bir soru (genellikle daha yeni olan) diğerinin kopyası olarak kapatılmalıdır. Bunu, bir kopya olarak kapatmak için oy vererek veya bunun için yeterli itibarınız yoksa , bunun bir kopya olduğunu belirtmek için bir bayrak kaldırarak belirtebilirsiniz. Aksi takdirde, cevabınızı bu soruya uygun hale getirdiğinizden ve aynı cevabı birden fazla yere yapıştırmadığınızdan emin olun .
elixenide
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.