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?
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?
Odpowiedzi:
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);
}
}
System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder
base.OnModelCreating(modelBuilder);
. Czy była to celowa czy tylko ofiara pisania kodu online zamiast w IDE?
Jeśli chcesz ustawić dokładność dla wszystkich decimals
w EF6, możesz zastąpić domyślną DecimalPropertyConvention
konwencję używaną w DbModelBuilder
:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<DecimalPropertyConvention>();
modelBuilder.Conventions.Add(new DecimalPropertyConvention(38, 18));
}
Domyślnie DecimalPropertyConvention
w EF6 odwzorowuje decimal
wł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);
}
}
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.
MethodInfo methodInfo = entityConfig.GetType().GetMethod("Property", new[] { lambdaExpression.GetType() });
aby uzyskać prawidłowe przeciążenie. wydaje się działać do tej pory.
Używając DecimalPrecisonAttribute
z 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 DecimalPropertyConvention
podobnych 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());
}
Precision
, zalecamy ustawienie górnej granicy na 28 (więc > 28
w twoim stanie). Zgodnie z dokumentacją MSDN System.Decimal
może reprezentować maksymalnie 28-29 cyfr precyzji ( msdn.microsoft.com/en-us/library/364x0z75.aspx ). Ponadto atrybut określa Scale
jako byte
, co oznacza, że warunek wstępny attribute.Scale < 0
jest niepotrzebny.
System.Decimal
nie. Dlatego nie ma sensu ustawiać górnej granicy warunku na wartość większą niż 28; System.Decimal
najwyraź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 numeric
typ PostgreSQL obsługuje do 131072 cyfr dokładności.
decimal(38,9)
kolumna z przyjemnością przytrzyma, System.Decimal.MaxValue
ale decimal(28,9)
kolumna nie. Nie ma powodu, aby ograniczać precyzję tylko do 28.
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.
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.
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:
[Column(TypeName = "decimal(18,2)")]
będzie to działać z pierwszymi migracjami kodu EF Core, jak opisano tutaj .
The store type 'decimal(18,2)' could not be found in the SqlServer provider manifest
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);
}
}
- 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
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);
});
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.
Remove<DecimalPropertyConvention>();
nadejdzie Add(new DecimalPropertyConvention(18, 4));
. Myślę, że to dziwne, że nie jest po prostu automatycznie zastępowane.
Za pomocą
System.ComponentModel.DataAnnotations;
Możesz po prostu umieścić ten atrybut w swoim modelu:
[DataType("decimal(18,5)")]
Więcej informacji na temat MSDN - aspekt Entity Data Model. http://msdn.microsoft.com/en-us/library/ee382834.aspx Pełna rekomendacja.
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)");
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, 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;
}
}
}
[Column(TypeName = "decimal(18,4)")]
atrybutu dla właściwości dziesiętnych