I have a value object named "WithdrawFee" 👍
public Fee WithdrawFee { get; private set; }
and the Fee value object is :
public class Fee : ValueObject<Fee>
{
public decimal Value { get; private set; }
public static Fee FromDecimal(decimal value) => new Fee(value);
public Fee(decimal value)
{
if (value < 0)
throw new ArgumentException("Fee is not valid");
Value = value;
}
public override int ObjectGetHashCode() => Value.GetHashCode();
public override bool ObjectIsEqual(Fee otherObject) => Value == otherObject.Value;
public override string ToString() => $"{Value}";
public static implicit operator decimal(Fee fee) => fee.Value;
}
that configured with fluent bellow :
builder.Property(c => c.WithdrawFee)
.HasConversion(c => c.Value, v => Fee.FromDecimal(v)).HasColumnType("decimal(18,8)");
THE PROBLEM:
the problem is when I run query below :
decimal testValue = 10.541m;
var result = await db.Networks.AsNoTracking()
.Where(c => c.WithdrawFee > testValue )
.ToListAsync();
it ran this sql query on db (I got it from sql profiler):
SELECT *
FROM [Networks]
WHERE CAST([WithdrawFee] AS decimal(18,2)) > CAST(10.541 AS decimal(18,2))
as you can see, it cast values to decimal(18,2) !
Why ef cast decimals ?
How can I fix this ?
ef core version : 5.0.10
.net version : 3.1
Related
I am using value objects as identities and would like to know how to best deal with nulls in EF core.
For example if I have an employee with an optional title (mr, mrs, etc):
public class Employee
: Entity,
IAggregateRoot
{
public EmployeeTitleId TitleId { get; private set; }
public EmployeeFirstName FirstName { get; private set; }
public EmployeeSurname Surname { get; private set; }
...
}
I could check for nulls everywhere in my code e.g.
if (employee.TitleId == null) ...
or I could use a default e.g.
if (employee.TitleId.Equals(EmployeeTitleId.None)) ...
with the EmployeeTitleId implemented as follows:
public class EmployeeTitleId
: Value<EmployeeTitleId>
{
public static readonly EmployeeTitleId None = new EmployeeTitleId();
protected EmployeeTitleId() { }
public EmployeeTitleId(Guid value)
{
if (value == default)
throw new ArgumentNullException(nameof(value), "Employee title id cannot be empty");
Value = value;
}
public Guid Value { get; internal set; }
public static implicit operator Guid(EmployeeTitleId self) => self.Value;
public static implicit operator EmployeeTitleId(string value)
=> new EmployeeTitleId(Guid.Parse(value));
public override string ToString() => Value.ToString();
}
I would prefer the second approach as it seems cleaner but I don't know if this is overkill. It would also require a bit more setup on the entity framework side e.g.:
builder.OwnsOne(_ => _.TitleId).Property(_ => _.Value)
.HasColumnName("TitleId").HasConversion(v => v == default ? (Guid?) null : v, v => v ?? EmployeeTitleId.None);
Does this seem like a viable approach or should I just stick to checking for null? If so, is there a convention I could use in the entity type configurations so that I don't have to manually set each HasConversion?
There is another way to filter the results globally. You can configure this in the OnModelCreating like:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// code omitted for brevity
modelBuilder.Entity<Employee>().HasQueryFilter(p => p.TitleId == null);
}
I have the following classes
public class Slot : Entity
{
public SnackPile SnackPile { get; set; }
public SnackMachine SnackMachine { get; set; }
public int Position { get; }
protected Slot()
{
}
public Slot(SnackMachine snackMachine, int position)
{
SnackMachine = snackMachine;
Position = position;
SnackPile = SnackPile.Empty;
}
}
public class Snack : AggregateRoot
{
public string Name { get; set; }
public Snack()
{
}
private Snack(long id, string name)
{
Id = id;
Name = name;
}
}
public class SnackPile : ValueObject
{
public Snack Snack { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
protected SnackPile()
{
}
public SnackPile(Snack snack, int quantity, decimal price)
{
Snack = snack;
Quantity = quantity;
Price = price;
}
protected override IEnumerable<object> GetEqualityComponents()
{
yield return Snack;
yield return Quantity;
yield return Price;
}
}
I'm trying to build my relationships using Entity Framework Core but my SnackPiles and Snacks are all null when trying to load them in my UI. However if I only set up my SnackMachines, all my of SnackPiles load fine but have null Snacks.
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<SnackMachine>(entity =>
{
entity.OwnsOne<Money>(e => e.MoneyInside, MoneyInside =>
{
MoneyInside.Property(mi => mi.OneCentCount).HasColumnName("OneCentCount");
MoneyInside.Property(mi => mi.TenCentCount).HasColumnName("TenCentCount");
MoneyInside.Property(mi => mi.QuarterCentCount).HasColumnName("QuarterCentCount");
MoneyInside.Property(mi => mi.OneDollarCount).HasColumnName("OneDollarCount");
MoneyInside.Property(mi => mi.FiveDollarCount).HasColumnName("FiveDollarCount");
MoneyInside.Property(mi => mi.TenDollarCount).HasColumnName("TenDollarCount");
}).Ignore(o => o.MoneyInTransaction);
});
builder.Entity<Slot>(entity =>
{
entity.Property(e => e.Position);
entity.OwnsOne<SnackPile>(e => e.SnackPile, SnackPile =>
{
SnackPile.Property(sp => sp.Quantity).HasColumnName("Quantity");
SnackPile.Property(sp => sp.Price).HasColumnName("Price").HasColumnType("Decimal");
});
});
}
}
I have two questions. Doing this, I get a shadow property called SnackPile_SnackId which I would like named SnackId but nothing I do accomplishes this without creating both properties and the SnackPile_SnackId is set up as the FK.
The next question, is.. is this relationship attainable in Entity Framework Core 3? It appears I have an Entity that has a value object containing the Id of another Entity which I would like to reference.
The result I would like to get can be done with NHibernate
public class SlotMap : ClassMap<Slot>
{
public SlotMap()
{
Id(x => x.Id);
Map(x => x.Position);
Component(x => x.SnackPile, y =>
{
y.Map(x => x.Quantity);
y.Map(x => x.Price);
y.References(x => x.Snack).Not.LazyLoad();
});
References(x => x.SnackMachine);
}
}
Further reference is that I'm following the DDDInPractice course on PluralSite which uses NHibernate (It's an amazing course and highly recommend). Using EF is a learning exercise to see the nuances. The course owner referred me to his blog post on the subject but there have been changes to EF since then. I have an ok understanding for a lot of these concepts but I'm stuck here.
Number 6 in the list:
https://enterprisecraftsmanship.com/posts/ef-core-vs-nhibernate-ddd-perspective/
The problem is that I wasn't using lazy loading.
I am using EF core 2.2, I am working on a search page, and I need to get only the newest(timestamp) form a custID. So the data can look like this.
AltId, CustID, ReceiveDate, name,...
1, 2, Null, bob...
1, 2, 2/9/2018, bob...
1, 2, 2/5/2018, bob...
1, 2, 1/10/2017, bob...
2, 5, 1/6/2018, Matt...
3, 7, 1/3/2018, Kelly...
4, 9, 1/5/2018, Sam...
The results I need are.. you can see I needed to filter out null and get the max date.
AltId, CustID, ReceiveDate, name,...
1, 2, 2/9/2018, bob...
2, 5, 1/6/2018, Matt...
3, 7, 1/3/2018, Kelly...
4, 9, 1/5/2018, Sam...
I am less worried about the details of the error. I don't want talk about the int and string. BUT rather why the .join is braking or changing the type of something.
Also, any advice on how to debug this low-level EF error would help.
if I remove the .join, I get all the record and my array of custTransations is populated. (as I would suspect)
if I remove the .include I get the correct filtered data but custTransations null (as I would suspect)
var query = Set.GroupBy(e => e.CustId)
.Select(e => new { CustId= e.Key, ReceiveDate = e.Max(m => m.ReceiveDate ) })
.AsQueryable();
data = await Set.Where(
e => e.IssueDate >= criteria.StartDate
&& e.IssueDate <= criteria.EndDate)
.Where(e => e.CustTransactions.Any(
cat =>cat.Cust.Member.AltId == criteria.AltId
&& cat.Cust.IsActive == true
&& cat.IsActive == true)
.Join(query,
outer => new { outer.CustId, outer.ReceiveDate },
inner => new { inner.CustId, inner.ReceiveDate },
(outer, inner) => outer
)
.Include(e => e.CustTransactions)
.AsNoTracking()
.ToListAsync();
```
No coercion operator is defined between types 'System.Int32' and 'System.String'.
Looks like a query generation bug/limitation in EF Core. There are still many query shapes that EF Core can't translate to SQL, but most of them should fail with an intelligible error message. This one just fails. Below is a repro and a workaround. Someone should see if this repros on EF Core 3, and open an issue on GitHub.
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace SomeUI
{
public class Product
{
public int Id { get; set; }
public string CategoryName { get; set; }
public DateTime UpdatedAt { get; set; }
public DateTime CreatedAt { get; set; }
public virtual ICollection<Transactions> Transactions { get; } = new HashSet<Transactions>();
}
public class Transactions
{
public int Id { get; set; }
public Product Product { get; set; }
public int Quantity { get; set; }
public DateTime TransDate { get; set; }
}
public class Db : DbContext
{
private string connectionString;
public Db() : this("Server=.;Database=EfCoreTest;Integrated Security=true")
{ }
public Db(string connectionString)
{
this.connectionString = connectionString;
}
public DbSet<Product> Products{ get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(connectionString, b => b.UseRelationalNulls())
.ConfigureWarnings(warnings => warnings.Throw(Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.QueryClientEvaluationWarning));
base.OnConfiguring(optionsBuilder);
}
}
public class Program
{
static void Main(string[] args)
{
using (var db = new Db())
{
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
var p = new Product() { CategoryName = "A" };
var tran = new Transactions() { Product = p, Quantity = 2, TransDate = DateTime.Now };
db.Products.Add(p);
db.SaveChanges();
}
using (var db = new Db())
{
var q = db.Products.GroupBy(p => p.CategoryName).Select(g => new { CategoryName = g.Key, UpdatedAt = g.Max(p => p.UpdatedAt) });
var q2 =
from p in db.Products.Include(p => p.Transactions)
join r in q
on new { p.CategoryName, p.UpdatedAt } equals new { r.CategoryName, r.UpdatedAt }
select p;
var q3 =
from p in db.Products.Include(p => p.Transactions)
where db.Products.Where(np => np.CategoryName == p.CategoryName).OrderByDescending(np => np.UpdatedAt).Take(1).Select(np => np.UpdatedAt).Contains(p.UpdatedAt)
select p;
var q4 =
db.Products
.Include(p => p.Transactions)
.Where(p => db.Products
.Where(np => np.CategoryName == p.CategoryName)
.OrderByDescending(np => np.UpdatedAt)
.Take(1)
.Select(np => np.UpdatedAt)
.Contains(p.UpdatedAt));
//var results = q2.ToList(); //fails
/*
System.InvalidOperationException
HResult=0x80131509
Message=No coercion operator is defined between types 'System.String' and 'System.Nullable`1[System.Int32]'.
Source=System.Linq.Expressions
StackTrace:
at System.Linq.Expressions.Expression.GetUserDefinedCoercionOrThrow(ExpressionType coercionType, Expression expression, Type convertToType)
*/
var results = q3.ToList(); //works
db.SaveChanges();
}
}
}
}
the Get(string _date) method below does not work. I get the following exception. I know EFCore is very limited but this exception does not help to see where the problem is exactly at. Can you please explain me what's causing this exception?
An unhandled exception occurred while processing the request.
ArgumentException: The given expression 'new EntryViewModel([x])' does not contain the searched expression '[x]' in a nested NewExpression with member assignments or a MemberBindingExpression.
Parameter name: fullExpression
InvokeMethod
TargetInvocationException: Exception has been thrown by the target of an invocation.
InvokeMethod
EntryController.cs
[HttpGet("{_date}")]
public async Task<IEnumerable<EntryViewModel>> Get(string _date)
{
DateTime date = DateTime.ParseExact(_date, "dd-MM-yyyy", CultureInfo.InvariantCulture);
User user = await _userManager.GetUserAsync(HttpContext.User);
var Entries = _context.Entries.Include(x => x.Budget)
.Where(x => x.User.Id == user.Id && x.Date >= date && x.Date < date.AddDays(7))
.Select(x=> new EntryViewModel(x))
.ToList();
return Entries;
}
EntryViewModel.cs
public class EntryViewModel
{
public int Id { get; set; }
public int BudgetId { get; set; }
public string Date { get; set; }
public int Duration { get; set; }
public string UserId { get; set; }
public EntryViewModel(Entry entry)
{
Id = entry.Id;
BudgetId = entry.BudgetId;
Date = entry.Date.ToString("dd-MM-yyyy", CultureInfo.InvariantCulture);
Duration = entry.Duration;
UserId = entry.UserId;
}
}
What this cryptic exception message is saying is that in EF, if you want to use x to construct a new object, it must be in the form new C { A = x.B, ... } (where C may be omitted to project to an anonymous type). EF does not support calling arbitrary functions (including constructors), only some specific functionality such as calling property setters is supported.
You can try as shown below then and let us know about the result.
var Entries = _context.Entries.Include(x => x.Budget)
.Where(x => x.User.Id == user.Id && x.Date >= date && x.Date < date.AddDays(7))
.Select(e=> new EntryViewModel
{
BudgetId=e.BudgetId,
Duration=e.Duration,
}).ToList();
Try adding AsNoTracking() to your query if you have no write
This question already has answers here:
Decimal precision and scale in EF Code First
(18 answers)
Closed 2 years ago.
How can I do this :
private decimal _SnachCount;
[Required]
[DataType("decimal(16 ,3)")]
public decimal SnachCount
{
get { return _SnachCount; }
set { _SnachCount = value; }
}
private decimal _MinimumStock;
[Required]
[DataType("decimal(16 ,3)")]
public decimal MinimumStock
{
get { return _MinimumStock; }
set { _MinimumStock = value; }
}
private decimal _MaximumStock;
[Required]
[DataType("decimal(16 ,3)")]
public decimal MaximumStock
{
get { return _MaximumStock; }
set { _MaximumStock = value; }
}
After generating the database by this part of my model , these three columns type are decimal(18,2),why?
what is this code error? how can i do that ?
The DataType Attribute is a Validation Attribute. You need to do that using the ModelBuilder.
public class MyContext : DbContext
{
public DbSet<MyClass> MyClass;
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<MyClass>().Property(x => x.SnachCount).HasPrecision(16, 3);
modelBuilder.Entity<MyClass>().Property(x => x.MinimumStock).HasPrecision(16, 3);
modelBuilder.Entity<MyClass>().Property(x => x.MaximumStock).HasPrecision(16, 3);
}
}
You can modify all decimal propreties in database. In your DBContext in method OnModelCreating add line:
modelBuilder.Properties<decimal>().Configure(c => c.HasPrecision(18, 3));
This is copied from the answer I posted to the same question over here; https://stackoverflow.com/a/15386883/1186032.
I had a nice time creating an Custom Attribute for this:
[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; }
}
using it like this
[DecimalPrecision(20,10)]
public Nullable<decimal> DeliveryPrice { get; set; }
and the magic happens at model creation with some reflection
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);
}
}
}
the first part is to get all classes in the model (my custom attribute is defined in that assembly so i used that to get the assembly with the model)
the second foreach gets all properties in that class with the custom attribute, and the attribute itself so i can get the precision and scale data
after that i have to call
modelBuilder.Entity<MODEL_CLASS>().Property(c=> c.PROPERTY_NAME).HasPrecision(PRECITION,SCALE);
so i call the modelBuilder.Entity() by reflection and store it in the entityConfig variable
then i build the "c => c.PROPERTY_NAME" lambda expression
After that, if the decimal is nullable i call the
Property(Expression<Func<TStructuralType, decimal?>> propertyExpression)
method (i call this by the position in the array, it's not ideal i know, any help will be much appreciated)
and if it's not nullable i call the
Property(Expression<Func<TStructuralType, decimal>> propertyExpression)
method.
Having the DecimalPropertyConfiguration i call the HasPrecision method.
So, what I got working for me is this:
public class RestaurantItemEntity : BaseEntity
{
[Column(TypeName = "VARCHAR(128)")]
[StringLength(128)]
[Required]
public string Name { get; set; }
[Column(TypeName = "VARCHAR(1024)")]
[StringLength(1024)]
public string Description { get; set; }
[Column(TypeName = "decimal(16,2)")]
[Required]
public decimal Price { get; set; }
[Required]
public RestaurantEntity Restaurant { get; set; }
}
This is EF Code first for .NET core.
You can also set the precision of decimals using the code-first model mapping approach like this:
public class MyEntityMapping : EntityTypeConfiguration<MyEntity>
{
public MyEntityMapping()
{
HasKey(x => x.Id);
Property(x => x.Id).IsRequired();
// .HasPrecision(precision, scale)
// 'precision' = total number of digits stored,
// regardless of where the decimal point falls
// 'scale' = number of decimal places stored
Property(x => x.DecimalItem).IsRequired().HasPrecision(16, 6);
}
}