Entity Frame 5.0 question.
I am new to Entity Frame, basically I have a table "MyPIN".
[Table("MyPIN")]
public class BatchPINDetail
{
public BatchPINDetail();
public int Number { get; set; }
I want to run a query
int x = the biggest Number FROM MyPIN
The correspond codes are:
public class InContext : DbContext
{
public InContext();
public InContext(string connectionString);
public DbSet<BatchDetail> BatchDetailsRecords { get; set; }
}
Question 1: I am not sure how to retrieve it.
Question 2: After I get the value, I want to reassign the value, say Number += 1; I need to write it to DB.
Thanks for help.
InContext inContext = new InContext(cnStr);
// get first biggest item
BatchPINDetail entity = inContext.BatchDetailsRecords
.OrderByDescending(x => x.Number)
.First();
// get all biggest items
BatchPINDetail entities = inContext.BatchDetailsRecords
.Where(x => x.Number == x.Max(x => x.Number))
.ToArray();
// and if you just want to get biggest number.
// but note that if you just change `num` nothing will happen.
int num = inContext.BatchDetailsRecords.Max(x => x.Number);
entity.Number += 1;
inContext.SaveChanges();
You can use direct approach like the following code and avoid forth and back call to the database.
InContext inContext = new InContext(cnStr);
string Sql="SELECT * FROM MyPIN where Number in (Select MAX(Number) from MyPIN)";
BatchPINDetail[] entities = inContext.BatchDetailsRecords.SqlQuery(Sql).ToArray();
Now you can do your changes and call SaveChanges() after that.
Related
Tested objects' classes as preamble
namespace EFConsoleApp.Models.Db
{
[Table("a")]
public class TableA
{
[Key]
[Column("id")]
public int Id { get; set; }
[Column("title")]
public string Title { get; set; }
[Column("amount")]
public int Amount { get; set; }
[Column("a_id")]
public int AId { get; set; }
}
}
namespace EFConsoleApp.DataAccesses.Db
{
public class ContextA : DbContext
{
public string DefaultSchema { get; private set; }
public ContextA() : base(GetConnecting(), true)
{
DefaultSchema = "public";
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
=> modelBuilder.HasDefaultSchema(DefaultSchema);
public static NpgsqlConnection GetConnecting()
{
return new NpgsqlConnection(ConfigurationManager.ConnectionStrings["postgreSql"].ToString());
}
public virtual DbSet<TableA> A { get; set; }
}
}
Question
Why I get the following exception when executing the code below with no data in table?
※ I'm using Entity Framework 6.2.0 and Npgsql 4.0.7 and .NET Framework 4.6 .
Exception:
System.InvalidOperationException:
The cast to value type 'System.Int32' failed because the materialized value is null.
Either the result type's generic parameter or the query must use a nullable type.
Executed code:
// _ctx's type is DataAccesses.Db.ContextA .
// ContextA table has no data.
var sum = _ctx.A
.Where(e => e.AId == aid)
.Select(e => e.Amount)
// .ToString() shows "SELECT \"Extent1\".\"amount\" FROM \"public\".\"a\" AS \"Extent1\" WHERE \"Extent1\".\"a_id\" = #p__linq__0"
.Sum();
And why I don't get the same exception when executing the code below with no data in list?
var list = new List<TableA>();
var qList = list.AsQueryable(); // To imitate return type of .Select()
// qList has no data.
var sum = qList
.Where(e => e.AId == 1)
.Select(e => e.Amount)
.Sum(); // sum = 0
Called LINQ methods are the same (as I think...), but result is different.
There are also other attempts
Attempt #1
var sum = <Resource>
.Where(e => e.AId == a
.Sum(e => e.Amount);
In case of EF, the same exception.
In case of in-memory object (new List<TableA>().AsQueryable()), sum is 0.
Attempt #2
var sum = (from a in <Resource> where a.AId == aid select a.Amount).Sum();
In case of EF, the same exception.
In case of in-memory object (new List<TableA>().AsQueryable()), sum is 0.
Attempt #3 (Tested only with EF)
var sum = _ctx.Database
.SqlQuery<int>("SELECT amount FROM a WHERE a_id = #par_a_id;",
new NpgsqlParameter("par_a_id", aid))
.Sum();
Surprisingly it works and sum is 0.
So, what's wrong with EF's implementation of LINQ?
Or problem with me because I can't understand something how it works under hood?
Update against #MindSwipe 's answer
I tried to execute sql with SUM directly in database and EF.
※ Still with no data in table.
SELECT SUM(amount) FROM a WHERE a_id = 1;
Query above returns null.
var sum = _ctx.Database
.SqlQuery<int>("SELECT SUM(amount) FROM a WHERE a_id = #par_a_id;",
new NpgsqlParameter("par_a_id", aid))
.Single();
Code above cause the same exception.
System.InvalidOperationException:
The cast to value type 'System.Int32' failed because the materialized value is null.
Either the result type's generic parameter or the query must use a nullable type.
So, at least I understood why exception occurs.
As conclusion
In case to avoid exceptions when working with SUM,
obviously, the code below is the most safe?
var sum = _ctx.Database
.Where(e => e.AId == aid)
.Select(e => e.Amount)
.DefaultIfEmpty(0)
.Sum();
Update against #Aron 's comment
Using IDbCommandInterceptor I got raw sql.
var sum = _ctx.A
.Where(e => e.AId == aid)
.Select(e => e.Amount) // #1
.Sum(); // #2
-- #1
SELECT "Extent1"."amount" FROM "public"."a" AS "Extent1" WHERE "Extent1"."a_id" = #p__linq__0"
-- #2
SELECT "GroupBy1"."A1" AS "C1" FROM (SELECT CAST (sum("Extent1"."amount") AS int4) AS "A1" FROM "public"."a" AS "Extent1" WHERE "Extent1"."a_id" = #p__linq__0) AS "GroupBy1"
Edit: Major re-write of the answer, the old one was incorrect.
In PostgresSQL SUM returns null when there are no elements to sum up. To test this, here's a short script:
drop table if exists temp;
create table temp (id integer, amount integer);
insert into temp (id, amount) values (1, 0);
select SUM(amount) from temp where id = 2;
Check the output and it will be null, not 0 or any other number
You can copy and paste the script into this site if you don't want to use a local database.
To protect against this case you need to either
Cast select nullable amount, and sum it, returning 0 if none were found, like so:
var sum = _ctx.A
.Where(x => x.AId == aid)
.Select(x => (int?) x.Amount)
.Sum() ?? 0;
Make Amount nullable by making it be public int? Amount {get; set;}
Currently I am looking for a way to get the sum of all piece counts from an enumerable while ignoring duplicates, all this while using method syntax. As things are right now my code will work, but I realize this is only temporary. More on this later.
Lets use the following class as an example
internal class Piece
{
public int Count { get; set; }
public DateTime Date { get; set; }
public string Description { get; set; }
}
This class is then used to create a list with the following information
List<Piece> pieces = new List<Piece>
{
new Piece(41,DateTime.Parse("2019-07-12"),"BB"),
new Piece(41,DateTime.Parse("2019-07-12"),"BB"),
new Piece(21,DateTime.Parse("2019-07-12"),"JP"),
new Piece(23,DateTime.Parse("2019-07-14"),"AA")
};
To do the sum, I came up with the following
int total = pieces.Where(x => x.Count > 0)
.GroupBy(x => x.Count, x => x.Date,
(piece, date) => new { Count = piece,Date = date})
.Sum(x => x.Count);
This is where things get tricky. If another piece were to be added with as follows
new Piece(23,DateTime.Parse("2019-07-14"),"AB")
that piece would be ignored due to how I am grouping. This is far from ideal.
I have found the following way to group by several columns
GroupBy( x => new {x.Count,x.Date,x.Description})
But I have found no way make it so I can use Sum on this Grouping. This grouping using the AnonymousType does not let me declare local variables (piece,date) as I am able to do in the prior GroupBy (as far as I know).
For now the code that I have will do the trick but it is only a matter of time before that is no longer the case.
Some extra details.
I am manipulating a query result using Razor, and I have no control on the data that I get from the server. Manipulating the data using linq is basically the only way I have at the moment.
Any help is greatly appreciated
For the count you just need this query:
int total = pieces
.Where(x => x.Count > 0)
.GroupBy(x => new { x.Count, x.Date, x.Description })
.Sum(g => g.Key.Count);
So you can access all key properties of the grouping.
This returns 85 for your initial sample and 108 if you add the new piece.
This seems simple enough but I'm not getting it for some reason.
Given:
public class Foo
{
public List<Bar> Bars { get; private set; }
public Bar Totals { get; private set; }
public Foo()
{
// Blah blah something to populate List of Bars
this.Bars = new List<Bar>()
{
new Bar("Some dude", 50, 1),
new Bar("Some other dude", 60,25)
};
// Calculate Total
var totals = Bars
.GroupBy(gb => gb.CustomerName) // When I comment out line i get "Bar does not contain a definition for "Sum" and no extension...." I want the sum of this without the group.
.Select(s => new
{
Cost = s.Sum(x => x.Cost),
Donation = s.Sum(x => x.Donation),
}
).ToList();
Totals = new Bar("Totals", totals[0].Cost, totals[0].Donation);
}
}
public class Bar
{
public string CustomerName { get; private set; }
public int Cost { get; private set; }
public int Donation { get; private set; }
public int Total { get { return Cost + Donation; } }
public Bar(string customerName, int cost, int donation)
{
this.CustomerName = customerName;
this.Cost = cost;
this.Donation = donation;
}
}
I'm having a few problems here:
-This works with a group by, but if i take out the group by which is my end goal I get "Bar does not contain a definition for "Sum" and no extension....". I want this sum on the entire collection, so do not want a group by.
-I'm creating an anon object before placing into a Bar because I'm not sure how to create a Bar without a parameterless constructor (and I can't add one to this particular class)
-I don't like accessing the "var totals" data using index 0 - should I not be ToListing at the end? If not, how do i access the properties? totals.Cost does not work.
Please help me figure out the proper way to get around my issue (specifically the 3 bullet points above this paragraph). Pretty new to the fluent (and linq in general)syntax and I'm trying to figure out the right way to do it.
EDIT:
thanks for the responses all. Taking kind of a combination of several answers really got me to what my end goal was (but biggest thanks D Stanley)
This is how I'm implementing now:
public Foo()
{
// ....
// Calculate Total
Totals = new Bar("Totals", Bars.Sum(s => s.Cost), Bars.Sum(s => s.Donation));
}
Guess I was just making it more complicated than it needed to be! :O
The s variable in the lambda is of type Bar if you remove the GroupBy. You want it to be List<Bar> instead in your case. So, what I think you want is something like:
var totalCosts = Bars.Sum(x => x.Cost);
var totalDonations = Bars.Sum(x => x.Donation);
but if i take out the group by I get "Bar does not contain a definition for "Sum"
That's because when you take out the GroupBy you're iterating over the individual items instead of a collection of groups. If you want to sum the entire collection use
var totals = new
{
Cost = Bars.Sum(x => x.Cost),
Donation = Bars.Sum(x => x.Donation),
}
;
or if you want a collection with one item, just change your GroupBy:
var totals = Bars
.GroupBy(gb => true) // trivial grouping
.Select(s => new Bar
{
Cost = s.Sum(x => x.Cost),
Donation = s.Sum(x => x.Donation),
}
).ToList();
-I'm casting to an anon object before placing into a Bar because I'm not sure how to cast it in without a parameterless constructor (and I can't add one to this particular class)
Just change your projection to
var totals = new Bar("Totals", Bars.Sum(x => x.Cost), Bars.Sum(x => x.Donation));
I don't like accessing the "var totals" data using index 0 - should I not be ToListing at the end? If not, how do i access the properties? totals.Cost does not work.
If you take out the group by you end up with just one object. If you have a colection with one item you could use First:
Totals = new Bar("Totals", totals.First().Cost, totals.First().Donation);
I want this sum on the entire collection, so do not want a group by.
Then use Sum on the collection
Bars.Sum(x => x.Cost)
I'm casting to an anon object before placing into a Bar because I'm not sure how to cast it in without a parameterless constructor (and I can't add one to this particular class)
You are not casting, you are creating anonymous objects
I don't like accessing the "var totals" data using index 0 - should I not be ToListing at the end? If not, how do i access the properties? totals.Cost does not work.
If you want single result use First.
It's simple enough, when you do Bars.Select(s =>, s is of type Bar and Bar has no definition of Sum. If you want the sum of all of it without any grouping, you can do:
Bars.Sum(b => b.Cost);
Bars.Sum(b => b.Donation);
You only need this :
Totals = new Bar("Totals", Bars.Sum(o => o.Cost), Bars.Sum(o => o.Donation));
I basically have a List that has a few columns in it. All I want to do is select whichever List item has the highest int in a column called Counted.
List<PinUp> pinned= new List<PinUp>();
class PinUp
{
internal string pn { get; set; }
internal int pi{ get; set; }
internal int Counted { get; set; }
internal int pp{ get; set; }
}
So basically I just want pinned[whichever int has highested Count]
Hope this makes sense
The problem is i want to remove this [whichever int has highested Count] from the current list. So I have to no which int it is in the array
One way, order by it:
PinUp highest = pinned
.OrderByDescending(p => p.Counted)
.First();
This returns only one even if there are multiple with the highest Counted. So another way is to use Enumerable.GroupBy:
IEnumerable<PinUp> highestGroup = pinned
.GroupBy(p => p.Counted)
.OrderByDescending(g => g.Key)
.First();
If you instead just want to get the highest Counted(i doubt that), you just have to use Enumerable.Max:
int maxCounted = pinned.Max(p => p.Counted);
Update:
The problem is i want to remove this [whichever int has highested Count] from the current list.
Then you can use List(T).RemoveAll:
int maxCounted = pinned.Max(p => p.Counted);
pinned.RemoveAll(p => p.Counted == maxCounted);
var excludingHighest = pinned.OrderByDescending(x => x.Counted)
.Skip(1);
If you need need to have a copy of the one being removed and still need to remove it you can do something like
var highestPinned = pinned.OrderByDescending(x => x.Counted).Take(1);
var excludingHighest = pinned.Except(highestPinned);
You can order it:
var pin = pinned.OrderByDescending(p => p.Counted).FirstOrDefault();
// if pin == null then no elements found - probably empty.
If you want to remove, you don't need an index:
pinned.Remove(pin);
it is a sorting problem.
Sort your list by Counted in descending order and pick the first item.
Linq has a way to do it:
var highest = pinned.OrderByDescending(p => p.Counted).FirstOrDefault();
Try the following:
PinUp pin = pinned.OrderByDescending(x => x.Counted).First();
So I have a little issue in sorting some data I have. In a Telerik Grid, I have a column called Requestor that displays the name of a person or Unit (group of people). The problem is, Requestor has two sources it can get it's data from. Here are the two sources.
1.) RequestorId: This is a foreign key to a table called Customer. Here, I store all the data for the user, including their full name. This field can be null btw.
2.) UnitId: This is another foreign key to a table called Units. Here, I store all the data for the Units, particularlly their names. This field can be null btw.
Here is the logic:
//Entity class that contains all the data for my grid
var purchaseOrders = _purchaseOrders.GetPurchaseOrders();
//Key is Id of PurchaseOrders, Value is name of requestor
var dictionary = new Dictionary<int, string>();
foreach (var purchaseOrder in purchaseOrders)
{
if (purchaseOrder.requestorId != null)
dictionary.add(purchaseOrder.Requestor.Fullname);
else
dictionary.add(purchaseOrder.unit.Fullname);
}
dictionary.orderby(x => x.value).ToDictionary(x => x.Key, x.Value);
var tempPurchaseOrders = new List<PurchaseOrder>();
foreach (var item in dictionary)
{
tempPurchaseOrders.Add(purchaseOrders.Where(x => x.Id == item.Key).FirstOrDefault());
}
purchaseOrders = tempPurchaseOrders.AsQueryable();
return purchaseOrders;
This logic returns an ordered list based on what I want to do, however, the problem is the amount of time it takes to process. It takes 1 minute to process. That's horrible obviously. Is there anyway to optimize this? I cut down the source after I return for the grid because there is no logical way to really cut it down beforehand.
Any help would be appreciated. Thanks.
Edit: I found out I no longer am required to use the RequestName field. That limits the data to two areas now. Still a minute to process though.
Did you try something like this:
return _purchaseOrders.GetPurchaseOrders().Select(i => new
{
OrderColumn = i.requestorId != null ? purchaseOrder.Requestor.Fullname : purchaseOrder.unit.Fullname,
// map other columns
})
.OrderBy(i => i.OrderColumn);
A bit like Sławomir Rosiek's solution (but entity framework won't accept that statement):
return _purchaseOrders.GetPurchaseOrders()
.OrderBy(o => o.unit.Fullname).ToList();
(since you don't use RequestName anymore).
Especially when GetPurchaseOrders() is an IQueryable from EF you delegate the sorting to the database engine because the sort expression becomes part of the SQL statement.
So I came up with my own solution. I first tried what both Sławomir Rosiek and Gert Arnold did. Unfortunately, like Gert mentioned, the first answer would not go through. The second one had similar issues.
In the end, I created a class to store the data from both Requestors and Units. It consisted of the following:
internal class RequestorData
{
public int entityId { get; set; }
public string Name { get; set; }
public bool isRequestorId { get; set; }
}
Next, I did the following.
//Entity class that contains all the data for my grid
var purchaseOrders = _purchaseOrders.GetPurchaseOrders();
var tempPurchaseOrders = new List<PurchaseOrder>();
var requestors = new List<RequestorData>();
var customers = purchaseOrders.Select(po => po.Requestor).Distinct().ToList();
var units = purchaseOrders.Select(po => po.Unit).Distinct().ToList();
foreach (var customer in customers)
{
if (customer != null)
requestors.Add(new RequestorData { entityId = customer.Id, Name = customer.FullName, isRequestorId = true });
}
foreach (var unit in units)
{
if (unit != null)
requestors.Add(new RequestorData { entityId = unit.Id, Name = unit.FullName, isRequestorId = false });
}
requestors = requestors.OrderBy(r => r.Name).ToList();
foreach (var requestor in requestors)
{
var id = requestor.entityId;
if (requestor.isRequestorId)
tempPurchaseOrders.AddRange(purchaseOrders.Where(po => po.RequestorId == id).ToList());
else
tempPurchaseOrders.AddRange(purchaseOrders.Where(po => po.UnitId == id).ToList());
}
purchaseOrders = tempPurchaseOrders.AsQueryable();
return purchaseOrders;
I ran this new rendition and have a 5-6 second time of wait. That's not perfect but much better than before. Thanks for all the help.