Sum in child collection - mysql

Using MySQL with EF6 throws an exception when I sum values from an empty child collection as the DefaultIfEmpty is not supported well with MySQL as related in bug #80127.
public class Foo
{
public int Id { get; set; }
public decimal Total { get; set; }
public virtual IList<Bar> Bars { get; set; }
}
public class Bar
{
public int Id { get; set; }
public int FooId { get; set; }
public virtual Foo Foo { get; set; }
public decimal Received { get; set; }
}
Using the recommended approach with DefaultIfEmpty throws an exception with invalid where clausule 'Project1'.'Id'. This is an old bug of MySQL.
var result = db.Foo.Select(f => new {
Total = f.Total,
Received = f.Bars.Select(b => b.Received).DefaultIfEmpty().Sum()
});
I'm using an inline if that works fine but generates an very ugly SQL with lots of inner queries and repetitions of select statements.
var result = db.Foo.Select(f => new {
Total = f.Total,
Received = f.Bars.Any() ? f.Bars.Sum(b => b.Received) : 0
});
Is there a better way to avoid DefaultIfEmpty?

The alternative of DefaultIfEmpty which I usually prefer is using cast operator to promote the non nullable type to nullable, which works (even) with MySQL connector.
Then the solution depends of your receiver class property type.
The best is if you can receive a nullable result, in which case the query is simple:
var result = db.Foo.Select(f => new {
Total = f.Total,
Received = f.Bars.Sum(b => (decimal?)b.Received)
});
If it needs to be a non nullable type, you can use null coalescing operator
var result = db.Foo.Select(f => new {
Total = f.Total,
Received = f.Bars.Sum(b => (decimal?)b.Received) ?? 0
});
but the generated SQL query is ugly and inefficient.
The best you can do in such case is to use (a quite annoying) double select trick:
var result = db.Foo.Select(f => new {
f.Total,
Received = f.Bars.Sum(b => (decimal?)b.Received)
})
.Select(r => new {
r.Total,
Received = r.Received ?? 0
};
or (a quite better) query syntax with let clause:
var result =
from f in db.Foos
let received = f.Bars.Sum(b => (decimal?)b.Received)
select new { f.Total, Received = received ?? 0 };
Tested on latest EF6.1.3 with MySQL Connector/Net 6.9.8

Related

MySQL and EF Core 6 error The LINQ expression could not be translated

I recently updated our project from EF Core 2.2.6 to 6.x (along with and upgrade from .NET core 3.1 to .NET 6) and now I'm get errors like the one stated in the title whenever the query gets even a little complicated. One of those cases is when you add a GroupBy clause. Below is an example of a failing query.
_context.MyTable
.Where(a => a.Name.Contains("service"))
.GroupBy(ss => ss.IsServiceSpecific)
The entire error is:
The LINQ expression 'DbSet< MyTable >()
.Where(a => a.Name.Contains("service"))
.GroupBy(ss => ss.IsServiceSpecific)' could not be translated. Either rewrite the query in a form that can be translated, or switch
to client evaluation explicitly by inserting a call to 'AsEnumerable',
'AsAsyncEnumerable', 'ToList', or 'ToListAsync'
The setup at this MySQL::Entity Framework Core Support URL is exactly what I did (there are only two steps to set it up). My DI config looks like this:
builder.Services.AddEntityFrameworkMySQL()
.AddDbContext<MydbContext>(options =>
{
options.UseMySQL(builder.Configuration.GetConnectionString("DefaultConnection"));
});
It will execute simple queries but more complex ones always generate this error. It says to rewrite the query and force client side evaluation by using AsEnumerable or ToList but I don't want to drag all that data to the client and I expect that a simple group by can be translated and handled server side.
I did find one article that talks about this problem but I'm not getting if it's suggesting an actual solution.
This shouldn't be this hard and I feel like I'm missing something simple.
Model
internal class Post
{
public int PostId { get; set; }
public string? Title { get; set; }
public string? Content { get; set; }
public int BlogId { get; set; }
}
DBContext
internal class BloggingContext : DbContext
{
public DbSet<Post>? Posts { get; set; }
public string DbPath { get; }
public BloggingContext()
{
var path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
DbPath = $"{path}{Path.DirectorySeparatorChar}blogging.db";
}
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlite($"Data Source={DbPath}");
}
Main
internal class Program
{
static void Main(string[] args)
{
using (var db = new BloggingContext())
{
var posts = db.Posts.Where(s => s.Title.Contains("Hello")).GroupBy(g => g.BlogId == 1994).Select(s => new { Key = s.Key, Counts = s.Count() }).ToList();
foreach (var p in posts)
{
Console.WriteLine(p);
}
}
}
}
Conclusion: You might add Select statement after GroupBy.

How to update an Entity in Entity Framework

I have a Date field in my DB and I'm trying to update it to the current Date when I press the submit button on my webpage but it does not update. I believe I'm doing the correct steps but here is my code.
Controller:
public ActionResult TakeInventory(int? AssetNum, string owners, string locationId, string clientId)
{
ViewBag.LocationId = new SelectList(db.Locations, "LocationKey", "LocationName");
ViewBag.ClientId = new SelectList(db.ClientSites, "ClientSiteKey", "ClientSiteName");
var records = from s in db.Assets select s;
if (AssetNum != 0)
{
records = records.Where(c => c.AssetKey == AssetNum);
}
if (!String.IsNullOrEmpty(owners))
{
records = records.Where(x => x.InventoryOwner.Equals(owners));
}
if (!String.IsNullOrEmpty(locationId))
{
int locnum = Convert.ToInt32(locationId);
records = records.Where(x => x.LocationKey == locnum);
}
if (!String.IsNullOrEmpty(clientId))
{
int clinum = Convert.ToInt32(clientId);
records = records.Where(x => x.ClientSiteKey == clinum);
}
else
{
return View(records);
}
return View(records);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult TakeInventory([Bind(Include = "InventoryDate")] Asset asset)
{
if (ModelState.IsValid)
{
db.Entry(asset).State = EntityState.Modified;
asset.InventoryDate = DateTime.Now;
db.Assets.Add(asset);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(asset);
}
View:
#foreach (var items in Model)
{
<p>Last Inventory Date: #Html.DisplayFor(modelItem => items.InventoryDate) </p>
<input type="submit" value="Submit" />
Model:
public partial class Asset
{
public System.DateTime InventoryDate { get; set; }
public Asset()
{
InventoryDate = DateTime.Now;
}
}
You want to retrieve the Asset entity again before updating again.
For example,
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult TakeInventory([Bind(Include = "InventoryDate")] Asset asset)
{
if (ModelState.IsValid)
{
var entity = (from s in db.Assets where AssetNum == asset.AssetNum Select s).FirstOrDefalt();
entity.InventoryDate = DateTime.Now;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(asset);
}
Is a bad practice:
asset.InventoryDate = DateTime.Now;
At least, you need:
1. SaveChanges() your DbContext
2. Your DateTime field in backend must be Nullable or NotNull in Db (there is no inmpicit conversion)
But the real trouble is timezones. Its all works fine, if you have only one instance in only one datacenter and all your clients is from only one small and beauty country (one timezone wide)
DateTime.Now returns you local mashine timezone time.
If you use your 'entity.InventoryDate' in any kind of requests query it can return confused rezults, and can be surprized with funny result: for ex., value with tomorrow datetime relatively to you :)
For Web-services always cast to UTC that kind of fields, or use triggers or default expression for this kind of fields inside your DB engine
P.S. Russia is 11 timezones wide, i know what i'm talking about
Why you are passing the Current date , there is no need for that you can you Sql build in function "GETDATE()" to Get the current Date

asp.net mvc 5 Dapper Json is mapping the whole model class

I am using Dapper in my ASP.NET MVC 5 application and in my query I only want 2 fields to return but the Json returns all of the fields. This is my model
public class thread
{
[Key]
public int id { get; set; }
public int? profileID { get; set; }
public int numberkeeper { get; set; }
public int? photocount { get; set; }
}
This is my controller..
[ResponseType(typeof(thread))]
public IHttpActionResult Getstream()
{
string Connectionstring = ConfigurationManager.ConnectionStrings["db"].ConnectionString;
using (System.Data.SqlClient.SqlConnection sqlConnection = new System.Data.SqlClient.SqlConnection(Connectionstring))
{
sqlConnection.Open();
var statevi = sqlConnection.Query<thread>("Select top 5 id,numberkeeper from threads").ToList();
if (statevi == null)
{
return NotFound();
}
return Ok(statevi);
}
}
That code returns Json as it is using .Net Web API,as you can see from the query I only want 2 fields returned. When I run it and see the Json it displays all fields (4) and off course the 2 fields not selected show up as null . I wanted so that the Json only shows the returnn of id and numberkeeper
Create a View Model class:
public class ThreadViewModel
{
public int id { get; set; }
public int numberkeeper { get; set; }
}
Let Dapper know you want it to create the ThreadViewModel for you:
var statevi = sqlConnection.Query<ThreadViewModel>("Select top 5 id,numberkeeper from threads").ToList();
This way you both query the database for the relevant properties and return just them to the client (without Dapper creating the full object with nulls).
If you create a new model that exposes the only two members that you want to render, that will prevent Web API from returning back additional JSON.
You could also convert the data after loading it into a new anonymous model using LINQ.
return Ok(statevi.Select(s => new { s.id, s.numberkeeper }));
If you want to keep the same model, but suppress null valued members Web API allows you to configure the JSON formatting to exclude null properties.
config.Formatters.JsonFormatter.SerializerSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
};
If you want to use 2 or selected rows from query then you can use query method and extension method...
1. LINQ query method
using (System.Data.SqlClient.SqlConnection sqlConnection = new System.Data.SqlClient.SqlConnection(Connectionstring))
{
sqlConnection.Open();
var statevi = sqlConnection.Query<thread>("Select top 5 id,numberkeeper from threads").ToList();
if (statevi == null)
{
return NotFound();
}
var result = (from d in statevi
select new { d.id, d.numberkeeper }).ToList();
return Ok(result);
}
Extension Method: change this syntax to result of query method of above
var result = query.Select(d => new { d.Id, d.Title }).ToList();
both will give result same.
let me tell if it is working fine for your project or not.

Signalr Return Model Object

The story is that, I have ROOM Model class. I want to return json with using Signalr. Is it possible ? If it is, how can i use it ?
PS: And I know that I dont return room objet to clients.
public List<RoomModel> GetRooms()
{
GameUser user = _gameService.GetUserByClientId(Context.ConnectionId);
var room = _gameService.GetAllowedRooms(user).Select(r => new RoomModel
{
Name = r.Name,
Count = 0,
Private = r.Private,
Closed = r.Closed,
}).ToList();
return room;
}
SignalR will automatically serialize your objects when you are sending them over to client. (I assume your client is javascript.)
As you can see in this example They are sending ShapeModel complex object to be processed in javascript. The serialization is all automated.
If your method from your example is a hub method, I suggest you end it differently. Instead of returning value, you would probably call a client event. So:
public class RoomHub : Hub {
public void GetRooms() {
List<Room> rooms = new List<Room>();
rooms.Add( new Room{ Name = "Room1", Count = 12, Closed = true, Private = false});
rooms.Add( new Room{ Name = "Room2", Count = 20, Closed = false, Private = true});
// sending a list of room objects
Clients.Client(Context.ConnectionId).roomInfo(rooms);
}
}
// Room class (your complex object)
public class Room
{
public string Name { get; set; }
public int Count { get; set; }
public bool Private { get; set; }
public bool Closed { get; set; }
}
See details about calling from hub methods here.
Then javascript client:
var roomHub = $.connection.roomHub;
roomHub.client.roomInfo = function (rooms) {
// the parameter rooms is a serialized List<Room>
// which here will be an array of room objects.
console.log(rooms);
// You can read the room properties as well
for (i=0; i<rooms.length; ++i) {
console.log(rooms[i].Name);
console.log(rooms[i].Count);
}
}
$.connection.hub.start().done(function () {
console.log("You are connected");
roomHub.server.getRooms();
});
On my browser console:

Linq-2-SQL Concat or Union Different types

I am trying to concatenate IQueryable where T can be different types.
So ((IQueryable<Person>)people).Concant((IQueryable<Account>)accounts).
I have created a structure like so:
public struct PerformanceStructure
{
public Entity Entity { get; set; }
public Entity Performance { get; set; }
}
I am building dynamic queries that follow this template:
var result = context.Accounts.Where(a => a.id == 1).Select(s => new PerformanceStructure { Entity = s });
result = result.Concat(context.Person.Where(p => p.id = 1).Select(s => new PerformanceStructure {Entity = s});
Execution looks like this:
var list = result.Skip(pageSize * (pageNumber - )).Take(pageSize);
When executing the query, I get the error Types in Union or Concat have different members assigned
What can I do to resolve this error but retrieve the two objects from that database?
In the end I want to paginate the query (thus the skip/take) based on some order.