Dokładność dziesiętna i skala w kodzie EF Najpierw

230

Eksperymentuję z tym podejściem opartym na pierwszym kodzie, ale teraz dowiaduję się, że właściwość typu System.Decimal jest odwzorowywana na kolumnę SQL typu dziesiętnego (18, 0).

Jak ustawić dokładność kolumny bazy danych?

Dave Van den Eynde
źródło
11
Jednym ze sposobów jest użycie [Column(TypeName = "decimal(18,4)")]atrybutu dla właściwości dziesiętnych
S.Serpooshan
[Column (TypeName = "decimal (18,4)")] działał świetnie !!!
Brian Rice,

Odpowiedzi:

257

Odpowiedź Dave'a Van den Eynde jest już nieaktualna. Istnieją 2 ważne zmiany, od wersji EF 4.1 klasa ModelBuilder ma teraz nazwę DbModelBuilder, a obecnie istnieje metoda DecimalPropertyConfiguration.HasPrecision, która ma sygnaturę:

public DecimalPropertyConfiguration HasPrecision(
byte precision,
byte scale )

gdzie precyzja jest całkowitą liczbą cyfr, które baza danych będzie przechowywać, niezależnie od tego, gdzie spada kropka dziesiętna, a skala jest liczbą miejsc dziesiętnych, które zapisze.

Dlatego nie ma potrzeby powtarzania właściwości, jak pokazano, ale można po prostu wywołać z

public class EFDbContext : DbContext
{
   protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
   {
       modelBuilder.Entity<Class>().Property(object => object.property).HasPrecision(12, 10);

       base.OnModelCreating(modelBuilder);
   }
}
AlexC
źródło
Dla każdego, kto ma problemy z DbModelBuilder, spróbujSystem.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder
Lloyd Powell,
1
Zauważyłem, że nigdy nie dzwoniłeś base.OnModelCreating(modelBuilder);. Czy była to celowa czy tylko ofiara pisania kodu online zamiast w IDE?
BenSwayne,
1
@BenSwayne dziękuję za miejsce, to moje pominięcie, a nie cokolwiek celowego. Zmienię odpowiedź.
AlexC
26
2 argumenty HasPrecision (precyzja, skala) są słabo udokumentowane. precyzja to całkowita liczba cyfr, które będzie przechowywać, niezależnie od tego, gdzie przypada kropka dziesiętna. Skala to liczba miejsc dziesiętnych, które będzie przechowywać.
Chris Moschini,
1
Czy istnieje konfiguracja EF, aby ustawić ją dla wszystkich właściwości dziesiętnych dla wszystkich jednostek w jednym miejscu? Zwykle używamy (19,4). Byłoby miło mieć to automatycznie zastosowane do wszystkich właściwości dziesiętnych, więc nie możemy zapomnieć o ustawieniu precyzji właściwości i pominięciu oczekiwanej precyzji w obliczeniach.
Mike de Klerk
89

Jeśli chcesz ustawić dokładność dla wszystkich decimalsw EF6, możesz zastąpić domyślną DecimalPropertyConventionkonwencję używaną w DbModelBuilder:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Conventions.Remove<DecimalPropertyConvention>();
    modelBuilder.Conventions.Add(new DecimalPropertyConvention(38, 18));
}

Domyślnie DecimalPropertyConventionw EF6 odwzorowuje decimalwłaściwości na decimal(18,2)kolumny.

Jeśli chcesz, aby poszczególne właściwości miały określoną dokładność, możesz ustawić dokładność właściwości encji na DbModelBuilder:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<MyEntity>().Property(e => e.Value).HasPrecision(38, 18);
}

Lub dodaj EntityTypeConfiguration<>dla encji, która określa precyzję:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Configurations.Add(new MyEntityConfiguration());
}

internal class MyEntityConfiguration : EntityTypeConfiguration<MyEntity>
{
    internal MyEntityConfiguration()
    {
        this.Property(e => e.Value).HasPrecision(38, 18);
    }
}
kjbartel
źródło
1
Moje ulubione rozwiązanie. Działa idealnie podczas korzystania z CodeFirst i migracji: EF szuka wszystkich właściwości we wszystkich klasach, w których używany jest „dziesiętny” i generuje migrację dla tych właściwości. Wspaniały!
okieh
75

Miło spędziłem czas, tworząc niestandardowy atrybut dla tego:

[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
public sealed class DecimalPrecisionAttribute : Attribute
{
    public DecimalPrecisionAttribute(byte precision, byte scale)
    {
        Precision = precision;
        Scale = scale;

    }

    public byte Precision { get; set; }
    public byte Scale { get; set; }

}

używając tego w ten sposób

[DecimalPrecision(20,10)]
public Nullable<decimal> DeliveryPrice { get; set; }

a magia dzieje się przy tworzeniu modelu z pewnym odbiciem

protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder)
{
    foreach (Type classType in from t in Assembly.GetAssembly(typeof(DecimalPrecisionAttribute)).GetTypes()
                                   where t.IsClass && t.Namespace == "YOURMODELNAMESPACE"
                                   select t)
     {
         foreach (var propAttr in classType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.GetCustomAttribute<DecimalPrecisionAttribute>() != null).Select(
                p => new { prop = p, attr = p.GetCustomAttribute<DecimalPrecisionAttribute>(true) }))
         {

             var entityConfig = modelBuilder.GetType().GetMethod("Entity").MakeGenericMethod(classType).Invoke(modelBuilder, null);
             ParameterExpression param = ParameterExpression.Parameter(classType, "c");
             Expression property = Expression.Property(param, propAttr.prop.Name);
             LambdaExpression lambdaExpression = Expression.Lambda(property, true,
                                                                      new ParameterExpression[]
                                                                          {param});
             DecimalPropertyConfiguration decimalConfig;
             if (propAttr.prop.PropertyType.IsGenericType && propAttr.prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
             {
                 MethodInfo methodInfo = entityConfig.GetType().GetMethods().Where(p => p.Name == "Property").ToList()[7];
                 decimalConfig = methodInfo.Invoke(entityConfig, new[] { lambdaExpression }) as DecimalPropertyConfiguration;
             }
             else
             {
                 MethodInfo methodInfo = entityConfig.GetType().GetMethods().Where(p => p.Name == "Property").ToList()[6];
                 decimalConfig = methodInfo.Invoke(entityConfig, new[] { lambdaExpression }) as DecimalPropertyConfiguration;
             }

             decimalConfig.HasPrecision(propAttr.attr.Precision, propAttr.attr.Scale);
        }
    }
}

pierwsza część polega na uzyskaniu wszystkich klas w modelu (mój niestandardowy atrybut jest zdefiniowany w tym zestawie, więc użyłem go, aby uzyskać zestaw z modelem)

drugi foreach otrzymuje wszystkie właściwości w tej klasie z atrybutem niestandardowym, a sam atrybut, dzięki czemu mogę uzyskać dokładność i skalować dane

potem muszę zadzwonić

modelBuilder.Entity<MODEL_CLASS>().Property(c=> c.PROPERTY_NAME).HasPrecision(PRECISION,SCALE);

więc wywołuję modelBuilder.Entity () przez odbicie i zapisuję go w zmiennej entityConfig, a następnie buduję wyrażenie lambda „c => c.PROPERTY_NAME”

Następnie, jeśli liczba dziesiętna jest zerowalna, wywołuję

Property(Expression<Func<TStructuralType, decimal?>> propertyExpression) 

metoda (nazywam to pozycją w tablicy, nie jest to idealne, wiem, każda pomoc będzie mile widziana)

a jeśli nie jest to zerowalne, dzwonię pod numer

Property(Expression<Func<TStructuralType, decimal>> propertyExpression)

metoda.

Mając DecimalPropertyConfiguration wywołuję metodę HasPrecision.

KinSlayerUY
źródło
3
Dzięki za to. Uratowało mnie to przed wygenerowaniem tysięcy wyrażeń lambda.
Sean
1
Działa to świetnie i jest super czyste! W przypadku EF 5 zmieniłem System.Data.Entity.ModelConfiguration.ModelBuilder na System.Data.Entity.DbModelBuilder
Colin
3
używam, MethodInfo methodInfo = entityConfig.GetType().GetMethod("Property", new[] { lambdaExpression.GetType() });aby uzyskać prawidłowe przeciążenie. wydaje się działać do tej pory.
fscan
3
Spakowałem to do biblioteki i ułatwiłem wywoływanie z DbContext: github.com/richardlawley/EntityFrameworkAttributeConfig (dostępny również przez nuget)
Richard
Richard, podoba mi się pomysł twojego projektu, ale czy jest coś w tym, co wymaga EF6? Użyłbym go, gdyby istniała wersja kompatybilna z EF5, dzięki czemu mogę go używać z moją wersją ODP.NET.
Patrick Szalapski,
50

Używając DecimalPrecisonAttributez KinSlayerUY, w EF6 możesz stworzyć konwencję, która będzie obsługiwać poszczególne właściwości, które mają atrybut (w przeciwieństwie do ustawiania DecimalPropertyConventionpodobnych w tej odpowiedzi, które wpłyną na wszystkie właściwości dziesiętne).

[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
public sealed class DecimalPrecisionAttribute : Attribute
{
    public DecimalPrecisionAttribute(byte precision, byte scale)
    {
        Precision = precision;
        Scale = scale;
    }
    public byte Precision { get; set; }
    public byte Scale { get; set; }
}

public class DecimalPrecisionAttributeConvention
    : PrimitivePropertyAttributeConfigurationConvention<DecimalPrecisionAttribute>
{
    public override void Apply(ConventionPrimitivePropertyConfiguration configuration, DecimalPrecisionAttribute attribute)
    {
        if (attribute.Precision < 1 || attribute.Precision > 38)
        {
            throw new InvalidOperationException("Precision must be between 1 and 38.");
        }

        if (attribute.Scale > attribute.Precision)
        {
            throw new InvalidOperationException("Scale must be between 0 and the Precision value.");
        }

        configuration.HasPrecision(attribute.Precision, attribute.Scale);
    }
}

Następnie w DbContext:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Conventions.Add(new DecimalPrecisionAttributeConvention());
}
kjbartel
źródło
@MichaelEdenfield Właściwie nie ma jednego z nich w EF6. Dlatego dodałem dwie odpowiedzi, jedną i drugą, o której mówiłeś. Jest to atrybut, który można umieścić na pojedynczej właściwości dziesiętnej zamiast wpływać na wszystkie właściwości dziesiętne w modelu.
kjbartel
moje złe, nie zauważyłem, że napisałeś je oba: \
Michael Edenfield
1
Jeśli masz zamiar sprawdzić granice Precision, zalecamy ustawienie górnej granicy na 28 (więc > 28w twoim stanie). Zgodnie z dokumentacją MSDN System.Decimalmoże reprezentować maksymalnie 28-29 cyfr precyzji ( msdn.microsoft.com/en-us/library/364x0z75.aspx ). Ponadto atrybut określa Scalejako byte, co oznacza, że ​​warunek wstępny attribute.Scale < 0jest niepotrzebny.
NathanAldenSr
2
@kjbartel Prawdą jest, że niektórzy dostawcy baz danych obsługują dokładności większe niż 28; jednak według MSDN System.Decimalnie. Dlatego nie ma sensu ustawiać górnej granicy warunku na wartość większą niż 28; System.Decimalnajwyraźniej nie mogą reprezentować tak dużych liczb. Należy również pamiętać, że ten atrybut jest przydatny dla dostawców danych innych niż SQL Server. Na przykład numerictyp PostgreSQL obsługuje do 131072 cyfr dokładności.
NathanAldenSr
1
@NathanAldenSr Jak powiedziałem, bazy danych używają dziesiętnego punktu stałego ( msdn ), podczas gdy System.Decimal jest zmiennoprzecinkowy . Oni są zupełnie inni. Na przykład decimal(38,9)kolumna z przyjemnością przytrzyma, System.Decimal.MaxValueale decimal(28,9)kolumna nie. Nie ma powodu, aby ograniczać precyzję tylko do 28.
kjbartel
47

Najwyraźniej możesz przesłonić metodę DbContext.OnModelCreating () i skonfigurować dokładność w następujący sposób:

protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Product>().Property(product => product.Price).Precision = 10;
    modelBuilder.Entity<Product>().Property(product => product.Price).Scale = 2;
}

Ale jest to dość żmudny kod, gdy musisz to zrobić ze wszystkimi swoimi właściwościami związanymi z ceną, więc wymyśliłem to:

    protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder)
    {
        var properties = new[]
        {
            modelBuilder.Entity<Product>().Property(product => product.Price),
            modelBuilder.Entity<Order>().Property(order => order.OrderTotal),
            modelBuilder.Entity<OrderDetail>().Property(detail => detail.Total),
            modelBuilder.Entity<Option>().Property(option => option.Price)
        };

        properties.ToList().ForEach(property =>
        {
            property.Precision = 10;
            property.Scale = 2;
        });

        base.OnModelCreating(modelBuilder);
    }

Dobrą praktyką jest wywoływanie metody podstawowej, gdy zastępujesz metodę, nawet jeśli implementacja podstawowa nic nie robi.

Aktualizacja: ten artykuł był również bardzo pomocny.

Dave Van den Eynde
źródło
10
Dzięki, to wskazało mi właściwy kierunek. W CTP5 zmieniono składnię, aby umożliwić dodanie Precision i Scale w tej samej instrukcji: modelBuilder.Entity <Product> () .Property (product => product.Price) .HasPrecision (6, 2);
płk.
2
Czy jednak nie byłoby miło mieć jakieś „domyślne” ustawienie dla wszystkich miejsc po przecinku?
Dave Van den Eynde,
3
Nie sądzę, aby dzwonienie base.OnModelCreating(modelBuilder);było konieczne. Z metadanych DbContext w VS: The default implementation of this method does nothing, but it can be overridden in a derived class such that the model can be further configured before it is locked down.
Matt Jenkins,
@Matt: To miłe, ale jako implementator nie powinienem się tym przejmować i zawsze dzwonię do bazy.
Dave Van den Eynde,
@ Dave i @Matt: Był komentarz, że „WAŻNE”, aby zadzwonić do bazy. Jest to dobra praktyka, ale gdy źródło EF ma pustą implementację, mylące jest twierdzenie, że jest Ważne. To sprawia, że ​​ludzie zastanawiają się, co robi baza. Byłem tak ciekawy, co było WAŻNE, że zdekompilowałem do ef5.0 do sprawdzenia. Nic. Tak dobry nawyk.
phil soady
30

Entity Framework Ver 6 (Alpha, rc1) ma coś o nazwie Konwencje niestandardowe . Aby ustawić dokładność dziesiętną:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Properties<decimal>().Configure(config => config.HasPrecision(18, 4));
}

Odniesienie:

mxasim
źródło
22
[Column(TypeName = "decimal(18,2)")]

będzie to działać z pierwszymi migracjami kodu EF Core, jak opisano tutaj .

Elnoor
źródło
1
Jeśli dodasz to do swojego modelu, możesz uzyskaćThe store type 'decimal(18,2)' could not be found in the SqlServer provider manifest
Savage
@ Savage wygląda na problem z dostawcą bazy danych lub wersją bazy danych
Elnoor
@Elnoor Savage jest poprawny, spowoduje to zgłoszenie błędu w EF Migrations 6.x. Starsza wersja non-Core nie obsługuje określania precyzji / skali za pomocą atrybutu Column i nie robi nic (domyślnie 18,2), jeśli użyjesz atrybutu DataType. Aby to działało poprzez Attribute w EF 6.x, musisz zaimplementować własne rozszerzenie do ModelBuilder.
Chris Moschini
1
@ChrisMoschini, zmieniłem swoją odpowiedź, wspominając EF Core. Dzięki
Elnoor
14

ta linia kodu może być prostszym sposobem na osiągnięcie tego samego:

 public class ProductConfiguration : EntityTypeConfiguration<Product>
    {
        public ProductConfiguration()
        {
            this.Property(m => m.Price).HasPrecision(10, 2);
        }
    }
armadillo.mx
źródło
9

- DLA EF CORE - przy użyciu System.ComponentModel.DataAnnotations;

use [Column( TypeName = "decimal( precyzja , skala )")]

Precyzja = całkowita liczba użytych znaków

Skala = całkowita liczba po kropce. (łatwo się pomylić)

Przykład :

public class Blog
{
    public int BlogId { get; set; }
    [Column(TypeName = "varchar(200)")]
    public string Url { get; set; }
    [Column(TypeName = "decimal(5, 2)")]
    public decimal Rating { get; set; }
}

Więcej informacji tutaj: https://docs.microsoft.com/en-us/ef/core/modeling/relational/data-types

sofsntp
źródło
3

W EF6

modelBuilder.Properties()
    .Where(x => x.GetCustomAttributes(false).OfType<DecimalPrecisionAttribute>().Any())
    .Configure(c => {
        var attr = (DecimalPrecisionAttribute)c.ClrPropertyInfo.GetCustomAttributes(typeof (DecimalPrecisionAttribute), true).FirstOrDefault();

        c.HasPrecision(attr.Precision, attr.Scale);
    });
użytkownik3332875
źródło
Ta odpowiedź wydaje się być uaktualnieniem do innej odpowiedzi, która definiuje atrybut, należy ją edytować w tej odpowiedzi
Rhys Bevilaqua,
3

Zawsze możesz powiedzieć EF, aby zrobił to z konwencjami w klasie Context w funkcji OnModelCreating w następujący sposób:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    // <... other configurations ...>
    // modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
    // modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
    // modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();

    // Configure Decimal to always have a precision of 18 and a scale of 4
    modelBuilder.Conventions.Remove<DecimalPropertyConvention>();
    modelBuilder.Conventions.Add(new DecimalPropertyConvention(18, 4));

    base.OnModelCreating(modelBuilder);
}

Dotyczy to tylko Code First EF fyi i dotyczy wszystkich typów dziesiętnych odwzorowanych na db.

Gecko IT
źródło
Nie działało, dopóki nie Remove<DecimalPropertyConvention>();nadejdzie Add(new DecimalPropertyConvention(18, 4));. Myślę, że to dziwne, że nie jest po prostu automatycznie zastępowane.
Mike de Klerk
2

Za pomocą

System.ComponentModel.DataAnnotations;

Możesz po prostu umieścić ten atrybut w swoim modelu:

[DataType("decimal(18,5)")]
VinnyG
źródło
1
jest to najłatwiejsza implementacja pod względem czytelności i prostoty. IMHO
ransems
11
Na msdn.microsoft.com/en-us/library/jj591583(v=vs.113).aspx ta odpowiedź jest nieprawidłowa. „Nie należy mylić atrybutu TypeName kolumny z DataType DataAnnotation. DataType jest adnotacją używaną w interfejsie użytkownika i jest ignorowana przez Code First”.
speckledcarp
2
@ransems Też tak myślałem, dopóki go nie przetestowałem i jak powiedziano powyżej, nie działa to w CodeFirst i nie migruje do bazy danych
RoLYroLLs
1

Więcej informacji na temat MSDN - aspekt Entity Data Model. http://msdn.microsoft.com/en-us/library/ee382834.aspx Pełna rekomendacja.

Jaider
źródło
To świetnie, ale jak to się ma do Kodeksu?
Dave Van den Eynde
Jest to przydatne, ale nadal nie mogę określić atrybutu [Precyzja] dla Dziesiętnego. Wykorzystałem więc rozwiązanie dostarczone przez @KinSlayerUY.
Colin,
1

Rzeczywiste dla EntityFrameworkCore 3.1.3:

jakieś rozwiązanie w OnModelCreating:

var fixDecimalDatas = new List<Tuple<Type, Type, string>>();
foreach (var entityType in builder.Model.GetEntityTypes())
{
    foreach (var property in entityType.GetProperties())
    {
        if (Type.GetTypeCode(property.ClrType) == TypeCode.Decimal)
        {
            fixDecimalDatas.Add(new Tuple<Type, Type, string>(entityType.ClrType, property.ClrType, property.GetColumnName()));
        }
    }
}

foreach (var item in fixDecimalDatas)
{
    builder.Entity(item.Item1).Property(item.Item2, item.Item3).HasColumnType("decimal(18,4)");
}

//custom decimal nullable:
builder.Entity<SomePerfectEntity>().Property(x => x.IsBeautiful).HasColumnType("decimal(18,4)");
Azamat
źródło
0

Niestandardowy atrybut KinSlayerUY działał dla mnie dobrze, ale miałem problemy ze ComplexTypes. Były one mapowane jako byty w kodzie atrybutu, więc nie mogły być następnie mapowane jako ComplexType.

Dlatego rozszerzyłem kod, aby to umożliwić:

public static void OnModelCreating(DbModelBuilder modelBuilder)
    {
        foreach (Type classType in from t in Assembly.GetAssembly(typeof(DecimalPrecisionAttribute)).GetTypes()
                                   where t.IsClass && t.Namespace == "FA.f1rstval.Data"
                                   select t)
        {
            foreach (var propAttr in classType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.GetCustomAttribute<DecimalPrecisionAttribute>() != null).Select(
                   p => new { prop = p, attr = p.GetCustomAttribute<DecimalPrecisionAttribute>(true) }))
            {

                ParameterExpression param = ParameterExpression.Parameter(classType, "c");
                Expression property = Expression.Property(param, propAttr.prop.Name);
                LambdaExpression lambdaExpression = Expression.Lambda(property, true,
                                                                         new ParameterExpression[] { param });
                DecimalPropertyConfiguration decimalConfig;
                int MethodNum;
                if (propAttr.prop.PropertyType.IsGenericType && propAttr.prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
                {
                    MethodNum = 7;
                }
                else
                {
                    MethodNum = 6;
                }

                //check if complextype
                if (classType.GetCustomAttribute<ComplexTypeAttribute>() != null)
                {
                    var complexConfig = modelBuilder.GetType().GetMethod("ComplexType").MakeGenericMethod(classType).Invoke(modelBuilder, null);
                    MethodInfo methodInfo = complexConfig.GetType().GetMethods().Where(p => p.Name == "Property").ToList()[MethodNum];
                    decimalConfig = methodInfo.Invoke(complexConfig, new[] { lambdaExpression }) as DecimalPropertyConfiguration;
                }
                else
                {
                    var entityConfig = modelBuilder.GetType().GetMethod("Entity").MakeGenericMethod(classType).Invoke(modelBuilder, null);
                    MethodInfo methodInfo = entityConfig.GetType().GetMethods().Where(p => p.Name == "Property").ToList()[MethodNum];
                    decimalConfig = methodInfo.Invoke(entityConfig, new[] { lambdaExpression }) as DecimalPropertyConfiguration;
                }

                decimalConfig.HasPrecision(propAttr.attr.Precision, propAttr.attr.Scale);
            }
        }
    }
Mark007
źródło
0

@ Mark007, zmieniłem kryteria wyboru typu, aby uzyskać dostęp do właściwości DbSet <> DbContext. Myślę, że jest to bezpieczniejsze, ponieważ zdarza się, że w podanej przestrzeni nazw istnieją klasy, które nie powinny być częścią definicji modelu lub są, ale nie są jednostkami. Albo twoje byty mogą znajdować się w oddzielnych przestrzeniach nazw lub osobnych zestawach i być połączone razem w jeden Kontekst.

Ponadto, choć mało prawdopodobne, nie sądzę, że można bezpiecznie polegać na porządkowaniu definicji metod, dlatego lepiej jest wyciągać je według listy parametrów. (.GetTypeMethods () to metoda rozszerzenia, którą zbudowałem do pracy z nowym paradygmatem TypeInfo i może spłaszczać hierarchie klas podczas wyszukiwania metod).

Należy pamiętać, że OnModelCreating deleguje do tej metody:

    private void OnModelCreatingSetDecimalPrecisionFromAttribute(DbModelBuilder modelBuilder)
    {
        foreach (var iSetProp in this.GetType().GetTypeProperties(true))
        {
            if (iSetProp.PropertyType.IsGenericType
                    && (iSetProp.PropertyType.GetGenericTypeDefinition() == typeof(IDbSet<>) || iSetProp.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>)))
            {
                var entityType = iSetProp.PropertyType.GetGenericArguments()[0];

                foreach (var propAttr in entityType
                                        .GetProperties(BindingFlags.Public | BindingFlags.Instance)
                                        .Select(p => new { prop = p, attr = p.GetCustomAttribute<DecimalPrecisionAttribute>(true) })
                                        .Where(propAttr => propAttr.attr != null))
                {
                    var entityTypeConfigMethod = modelBuilder.GetType().GetTypeInfo().DeclaredMethods.First(m => m.Name == "Entity");
                    var entityTypeConfig = entityTypeConfigMethod.MakeGenericMethod(entityType).Invoke(modelBuilder, null);

                    var param = ParameterExpression.Parameter(entityType, "c");
                    var lambdaExpression = Expression.Lambda(Expression.Property(param, propAttr.prop.Name), true, new ParameterExpression[] { param });

                    var propertyConfigMethod =
                        entityTypeConfig.GetType()
                            .GetTypeMethods(true, false)
                            .First(m =>
                            {
                                if (m.Name != "Property")
                                    return false;

                                var methodParams = m.GetParameters();

                                return methodParams.Length == 1 && methodParams[0].ParameterType == lambdaExpression.GetType();
                            }
                            );

                    var decimalConfig = propertyConfigMethod.Invoke(entityTypeConfig, new[] { lambdaExpression }) as DecimalPropertyConfiguration;

                    decimalConfig.HasPrecision(propAttr.attr.Precision, propAttr.attr.Scale);
                }
            }
        }
    }



    public static IEnumerable<MethodInfo> GetTypeMethods(this Type typeToQuery, bool flattenHierarchy, bool? staticMembers)
    {
        var typeInfo = typeToQuery.GetTypeInfo();

        foreach (var iField in typeInfo.DeclaredMethods.Where(fi => staticMembers == null || fi.IsStatic == staticMembers))
            yield return iField;

        //this bit is just for StaticFields so we pass flag to flattenHierarchy and for the purpose of recursion, restrictStatic = false
        if (flattenHierarchy == true)
        {
            var baseType = typeInfo.BaseType;

            if ((baseType != null) && (baseType != typeof(object)))
            {
                foreach (var iField in baseType.GetTypeMethods(true, staticMembers))
                    yield return iField;
            }
        }
    }
Eniola
źródło
Właśnie zdałem sobie sprawę, że przy takim podejściu nie radziłem sobie z ComplexTypes. Zmienię to później.
Eniola
Jednak rozwiązanie zaproponowane przez Briana jest proste, eleganckie i działa. Nie będę przedstawiał żadnych kategorycznych stwierdzeń dotyczących wydajności, ale jazda z już odzwierciedloną właściwością PropertyInfo zamiast polowania na nią powinna dać lepszą wydajność na bardzo dużych modelach (rzędu 200 i więcej).
Eniola