Convert datetime to a formatted string inside a LINQ-to-entities query - entity-framework

How can I convert DateTime into a formatted string?
This is the line in the following query that needs help:
StartDate = string.Format("{0:dd.MM.yy}", p.StartDate)
The whole query:
var offer = (from p in dc.CustomerOffer
join q in dc.OffersInBranch
on p.ID equals q.OfferID
where q.BranchID == singleLoc.LocationID
let value = (p.OriginalPrice - p.NewPrice) * 100 / p.OriginalPrice
orderby value descending
select new Offer()
{
Title = p.OfferTitle,
Description = p.Description,
BestOffer = value,
ID = p.ID,
LocationID = q.BranchID,
LocationName = q.CustomerBranch.BranchName,
OriginalPrice = SqlFunctions.StringConvert((decimal)p.OriginalPrice),
NewPrice = SqlFunctions.StringConvert((decimal)p.NewPrice),
StartDate = string.Format("{0:dd.MM.yy}", p.StartDate)
}).First();
I get the following error message:
LINQ to Entities does not recognize the method 'System.String ToString(System.String)' method, and this method cannot be translated into a store expression.

Another option is using SqlFunctions.DateName, your code will be like this:
var offer = (from p in dc.CustomerOffer
join q in dc.OffersInBranch
on p.ID equals q.OfferID
where q.BranchID == singleLoc.LocationID
let value = (p.OriginalPrice - p.NewPrice) * 100 / p.OriginalPrice
orderby value descending
select new
{
Title = p.OfferTitle,
Description = p.Description,
BestOffer = value,
ID = p.ID,
LocationID = q.BranchID,
LocationName = q.CustomerBranch.BranchName,
OriginalPrice = SqlFunctions.StringConvert((decimal)p.OriginalPrice),
NewPrice = SqlFunctions.StringConvert((decimal)p.NewPrice),
StartDate = SqlFunctions.DateName("day", p.StartDate) + "/" + SqlFunctions.DateName("month", p.StartDate) + "/" + SqlFunctions.DateName("year", p.StartDate)
})
I found it useful if you don't want to add an extra select new block.

EDIT: Now that I understand the question, I'm giving it another shot :)
var offer = (from p in dc.CustomerOffer
join q in dc.OffersInBranch
on p.ID equals q.OfferID
where q.BranchID == singleLoc.LocationID
let value = (p.OriginalPrice - p.NewPrice) * 100 / p.OriginalPrice
orderby value descending
select new
{
Title = p.OfferTitle,
Description = p.Description,
BestOffer=value,
ID=p.ID,
LocationID=q.BranchID,
LocationName=q.CustomerBranch.BranchName,
OriginalPrice=SqlFunctions.StringConvert((decimal)p.OriginalPrice),
NewPrice=SqlFunctions.StringConvert((decimal)p.NewPrice),
StartDate=p.StartDate
})
.ToList()
.Select(x => new Offer()
{
Title = x.OfferTitle,
Description = x.Description,
BestOffer=value,
ID=x.ID,
LocationID=x.BranchID,
LocationName=x.CustomerBranch.BranchName,
OriginalPrice=x.OriginalPrice,
NewPrice=x.NewPrice,
StartDate=x.StartDate.ToString("dd.MM.yy")
}).First();
I know it's a bit long, but that's the problem with Linq To SQL.
When you use linq, the database call isn't executed until you use something such as ToList() or First() that results in actual objects. Once that SQL call is executed by the first .First() call, you're now working with .NET types, and can use DateTime stuff.

I ended up using the sql function FORMAT; here's a simplified version of this implementation:
https://weblogs.asp.net/ricardoperes/registering-sql-server-built-in-functions-to-entity-framework-code-first
First you need to define the function in EF:
public class FormatFunctionConvention : IStoreModelConvention<EdmModel>
{
public void Apply(EdmModel item, DbModel model)
{
var payload = new EdmFunctionPayload
{
StoreFunctionName = "FORMAT",
Parameters = new[] {
FunctionParameter.Create("value", PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.DateTime), ParameterMode.In),
FunctionParameter.Create("format", PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.String), ParameterMode.In)
},
ReturnParameters = new[] {
FunctionParameter.Create("result", PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.String), ParameterMode.ReturnValue)
},
Schema = "dbo",
IsBuiltIn = true
};
item.AddItem(EdmFunction.Create("FORMAT", "CodeFirstDatabaseSchema", item.DataSpace, payload, null));
}
}
Then define it as C# methods:
public static class SqlFunctions
{
[DbFunction("CodeFirstDatabaseSchema", "FORMAT")]
public static String Format(this DateTime value, string format)
{
return value.ToString(format);
}
[DbFunction("CodeFirstDatabaseSchema", "FORMAT")]
public static String Format(this DateTime? value, string format)
{
return value?.ToString(format);
}
}
Register it in your DbContext:
public class SqlDb : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Add(new FormatFunctionConvention());
}
}
And finally, you can call it like so:
var x = db.MyItems.Select(i => new { FormattedDate = SqlFunctions.Format(i.MyDate, "MM/dd/yyyy") }).ToArray();

That is what we did, we added a new function to the class and we query the date as normal in the query:
[ComplexType]
public class Offer
{
public DateTime StartDate
{
get;
set;
}
public String Title
{
get;
set;
}
/*Other fields*/
.
.
.
public string FormattedDate(string format)
{
return Date.ToString(format);
}
}
var offer = (from p in dc.CustomerOffer
join q in dc.OffersInBranch
on p.ID equals q.OfferID
where q.BranchID == singleLoc.LocationID
let value = (p.OriginalPrice - p.NewPrice) * 100 / p.OriginalPrice
orderby value descending
select new Offer()
{
Title = p.OfferTitle,
Description = p.Description,
BestOffer = value,
ID = p.ID,
LocationID = q.BranchID,
LocationName = q.CustomerBranch.BranchName,
OriginalPrice = SqlFunctions.StringConvert((decimal)p.OriginalPrice),
NewPrice = SqlFunctions.StringConvert((decimal)p.NewPrice),
StartDate = p.StartDate
}).First();
Then you can just call the FormattedDate field passing the desired format.
edit1.Text = offer.FormattedDate("dd.MM.yy");
Or can can define it as a field with just the getter:
public string FormattedDate
{
get { return Date.ToString("dd.MM.yy") };
}
edit1.Text = offer.FormattedDate;
In case you class is an Entity, you need to declare a new partial of that class and add the field.
Hope this help someone.

In vb (valid to c# too changing syntax):
Imports System.Data.Entity
...
query.Select(Function(x) New MyObject With {
...
.DateString = DbFunctions.Right("00" & x.DateField.Day, 2) & "/" & DbFunctions.Right("00" & x.DateField.Month, 2) & "/" & x.DateField.Year
...
}).ToList()
Note: ToList(), ToEnumerable() are not the way because its executes a query, the user wants linq to sql..

The easiest and most efficient way I have found to do string formats on numeric or datetime objects is by using string interpolation. It will bring back the actual DateTime/int/float/double/etc.. objects in the SQL query, and then client side it will do the string format during projection. I modified your query below, note how OriginalPrice, NewPrice, and StartDate are converted:
var offer = (from p in dc.CustomerOffer
join q in dc.OffersInBranch
on p.ID equals q.OfferID
where q.BranchID == singleLoc.LocationID
let value = (p.OriginalPrice - p.NewPrice) * 100 / p.OriginalPrice
orderby value descending
select new Offer()
{
Title = p.OfferTitle,
Description = p.Description,
BestOffer = value,
ID = p.ID,
LocationID = q.BranchID,
LocationName = q.CustomerBranch.BranchName,
OriginalPrice = $"{p.OriginalPrice:C2}",
NewPrice = $"{p.NewPrice:C2}",
StartDate = $"{p.StartDate:dd.MM.yy}"
}).First();

if it's a datetime you need to use the .ToShortDateString(). But you also need to declare it AsEnumerable().
var offer = (from p in dc.CustomerOffer.AsEnumerable()
join q in dc.OffersInBranch
on p.ID equals q.OfferID
where q.BranchID == singleLoc.LocationID
let value = (p.OriginalPrice - p.NewPrice) * 100 / p.OriginalPrice
orderby value descending
select new
{
Title = p.OfferTitle,
Description = p.Description,
BestOffer=value,
ID=p.ID,
LocationID=q.BranchID,
LocationName=q.CustomerBranch.BranchName,
OriginalPrice=SqlFunctions.StringConvert((decimal)p.OriginalPrice),
NewPrice=SqlFunctions.StringConvert((decimal)p.NewPrice),
StartDate=p.StartDate
})
.ToList()
.Select(x => new Offer()
{
Title = x.OfferTitle,
Description = x.Description,
BestOffer=value,
ID=x.ID,
LocationID=x.BranchID,
LocationName=x.CustomerBranch.BranchName,
OriginalPrice=x.OriginalPrice,
NewPrice=x.NewPrice,
StartDate=x.StartDate.ToShortDateString()
}).First();

Related

How to loop through dbcontext all dbset in Entity Framework Core to get count?

I have 20 Dbsets in my context which I want to get the row count for each dbset to make sure all dbset row count is 0. To get the count for one dbset, this is my code:
var person = context.Persons.Count();
Is there a way to loop through the context, get the count for each dbset dynamically?
There is solution. Usage is simple:
var tablesinfo = ctx.GetTablesInfo();
if (tablesinfo != null)
{
var withRecords = tablesinfo
.IgnoreQueryFilters()
.Where(ti => ti.RecordCount > 0)
.ToArray();
}
Extension returns IQueryable<TableInfo> and you can reuse this query later. Probably you will need to filter out Views, but I think you can handle that. Note that IgnoreQueryFilters can be important if you have Global Query Filters defined.
What extension do:
It scans Model for entity types registered for particular DbContext and generates big Concat of Count queries. Here we have to do that via grouping by constant value.
Schematically it will generate the following LINQ query:
var tablesinfo =
ctx.Set<Entity1>.GroupBy(e => 1).Select(g => new TableInfo { TableName = "Entity1", RecordCount = g.Count()})
.Concat(ctx.Set<Entity2>.GroupBy(e => 1).Select(g => new TableInfo { TableName = "Entity2", RecordCount = g.Count()}))
.Concat(ctx.Set<Entity3>.GroupBy(e => 1).Select(g => new TableInfo { TableName = "Entity3", RecordCount = g.Count()}))
...
Which wll be converted to the following SQL:
SELECT "Entity1" AS TableName, COUNT(*) AS RecordCount FROM Entity1
UNION ALL
SELECT "Entity2" AS TableName, COUNT(*) AS RecordCount FROM Entity2
UNION ALL
SELECT "Entity3" AS TableName, COUNT(*) AS RecordCount FROM Entity3
...
Implementation:
public static class QueryableExtensions
{
public class TableInfo
{
public string TableName { get; set; } = null!;
public int RecordCount { get; set; }
}
public static IQueryable<TableInfo> GetTablesInfo(this DbContext ctx)
{
Expression query = null;
IQueryProvider provider = null;
var ctxConst = Expression.Constant(ctx);
var groupingKey = Expression.Constant(1);
// gathering information for MemberInit creation
var newExpression = Expression.New(typeof(TableInfo).GetConstructor(Type.EmptyTypes));
var tableNameProperty = typeof(TableInfo).GetProperty(nameof(TableInfo.TableName));
var recordCountProperty = typeof(TableInfo).GetProperty(nameof(TableInfo.RecordCount));
foreach (var entityType in ctx.Model.GetEntityTypes())
{
var entityParam = Expression.Parameter(entityType.ClrType, "e");
var tableName = entityType.GetTableName();
// ctx.Set<entityType>()
var setQuery = Expression.Call(ctxConst, nameof(DbContext.Set), new[] {entityType.ClrType});
// here we initialize IQueryProvider, which is needed for creating final query
provider ??= ((IQueryable) Expression.Lambda(setQuery).Compile().DynamicInvoke()).Provider;
// grouping paraneter has generic type, we have to specify it
var groupingParameter = Expression.Parameter(typeof(IGrouping<,>).MakeGenericType(typeof(int), entityParam.Type), "g");
// g => new TableInfo { TableName = "tableName", RecordCount = g.Count() }
var selector = Expression.MemberInit(newExpression,
Expression.Bind(tableNameProperty, Expression.Constant(tableName)),
Expression.Bind(recordCountProperty,
Expression.Call(typeof(Enumerable), nameof(Enumerable.Count), new[] {entityParam.Type}, groupingParameter)));
// ctx.Set<entityType>.GroupBy(e => 1)
var groupByCall = Expression.Call(typeof(Queryable), nameof(Queryable.GroupBy), new[]
{
entityParam.Type,
typeof(int)
},
setQuery,
Expression.Lambda(groupingKey, entityParam)
);
// ctx.Set<entityType>.GroupBy(e => 1).Select(g => new TableInfo { TableName = "tableName", RecordCount = g.Count()}))
groupByCall = Expression.Call(typeof(Queryable), nameof(Queryable.Select),
new[] {groupingParameter.Type, typeof(TableInfo)},
groupByCall,
Expression.Lambda(selector, groupingParameter));
// generate Concat if needed
if (query != null)
query = Expression.Call(typeof(Queryable), nameof(Queryable.Concat), new[] {typeof(TableInfo)}, query,
groupByCall);
else
query = groupByCall;
}
// unusual situation, but Model can have no registered entities
if (query == null)
return null;
return provider.CreateQuery<TableInfo>(query);
}
}

EF Core - how to select count from child table based on foreign key

TableB has a field TableAId which is linked to the Id of TableA. I want to select the count from TableB based on TableAId like -
SELECT *,
(SELECT COUNT(*) FROM tableB where tableB.TableAId = tableA.Id) as count
FROM tableA
So far I have the code:
var data = _context.TableA.AsQueryable();
...
data = data.Select(l => l.TableAId= p.Id).Count();
But on the last line, p is not recognized as a variable.
How do I make this work?
EDIT :
my original query is quiet complex and already filtering data
var data = _context.TableA.AsQueryable();
data = data.Include(p => p.SomeClassA)
.Include(p => p.SomeClassB);
data = data.Where(p => p.Id == somevalue);
data = data.Where(p => p.SomeClassA.Name.Contains(someothervalue));
data = data.Where(p => p.SomeClassA.SomeField.Contains(yetanothervalue));
I tried adding this but it cannot compile
(TableAId & Count do not exist):
data = data.Join(
_context.TableB,
groupByQuery => groupByQuery.TableAId ,
TableA => TableA.Id,
(groupByQuery, TableAItem) => new
{
TableAId = groupByQuery.Id,
Count = groupByQuery.Count,
TableAItem = TableAItem
}
);
If you are just interested in count, then do the following:
var data = _context.TableB.AsQueryable();
var groupByCountQuery = data.GroupBy(a=>a.TableAId, (tableAId, tableBItems) => new
{
TableAId = tableAId,
Count = tableBItems.Count()
});
var result = groupByCountQuery.ToList(); // or change to ToListAsync()
This will give you the count based on TableAId.
If you need the tableA items as well in the result, following ca be done:
var groupByCountQuery = data.GroupBy(a=>a.TableAId, (tableAId, tableBItems) => new
{
TableAId = tableAId,
Count = tableBItems.Count()
}).Join(_context.TableA,
groupByQuery => groupByQuery.TableAId,
tableA => tableA.Id,
(groupByQuery , tableA) => new {
TableAId = groupByQuery.TableAId,
Count = groupByQuery.Count,
TableAItem = tableA
} );
Let's say TableA has properties - Id, FieldA1 and FieldA2.
First, you have to include TableB in your query so that you can take its count, like -
var data = _context.TableA.Include(p=> p.TableB).AsQueryable();
Then in the Select method you have to create a new object with TableA's properties and TableB's count, like -
var list = data.Select(p =>
new
{
Id = p.Id,
A1 = p.FieldA1,
A2 = p.FieldA2,
Count = p.OrderLines.Count
}).ToList();
Notice, this is assigned to a new variable list. That is because it does not return a list of TableA, it returns a list of an anonymous object with properties - Id, A1, A2 and Count. Therefore, you cannot assign it to the previously declared data variable, because data is of type IQueryable<TableA>.
Alternatively, you can declare a class to hold the data values, like -
public class MyData
{
public int Id { get; set; }
public string A1 { get; set; }
public string A2 { get; set; }
public int Count { get; set; }
}
and use it like -
var list = data.Select(p =>
new MyData
{
Id = p.Id,
A1 = p.FieldA1,
A2 = p.FieldA2,
Count = p.OrderLines.Count
}).ToList();
If you want to start from TableA, you can use the following linq query:
var data = _context.TableAs.AsQueryable();
var x = (from a in data
join b in _context.TableBs on a.Id equals b.TableAId
group a.TableB by a.Id into g
select new
{
TableAId = g.Key,
TableBItem = g.FirstOrDefault(),
Count = g.Count()
}).ToList();
Result:

Return unique values only in Webapi controller

I have the following line in a WebApi controller;
string Codes = i.Products.FirstOrDefault().Code
As the line states, it gets the code, from the first Product.
But, what I really want it to do is to get all unique codes, and return them as a comma separated string.
So, let's say, there are 6 related products, and they have the following codes:
45
54
45
120
54
45
Right now, the statement just returns "45", given the above data.
But I want the statement above to return "45, 54, 120" (as a string).
How do I do this?
Complete code:
public System.Data.Entity.DbSet<WebAPI.Models.Product> Products { get; set; }
private ApplicationDbContext db = new ApplicationDbContext();
var product = await db.Products.Select(i =>
new ProductDTO()
{
Id = i.Id,
Created = i.Created,
Title = i.Title,
Codes = i.Products.FirstOrDefault().Code
}).SingleOrDefaultAsync(i => i.Id == id);
To convert to a comma separated list:
IEnumerable<string> distinctCodes = i.Products.Select(product => product.Code).Distinct();
return string.Join(",", distinctCodes);
But it's probably better if your controller returns a collection of string instead of a concatenated string.
Edit, after OP code update:
var DBProduct = await db.Products.SingleOrDefaultAsync(i => i.Id == id);
IEnumerable<string> productCodes = DBProduct.Products.Select(p => p.Code).Distinct();
var product = new ProductDTO()
{
Id = DBProduct.Id,
Created = DBProduct.Created,
Title = DBProduct.Title,
Codes = string.Join(",", productCodes)
};
I think Distinctand String.Join works for you.Please try this:
var product = await db.Products.AsEnumerable()//Turn AsEnumarable
.Select(i =>
new ProductDTO()
{
Id = i.Id,
Created = i.Created,
Title = i.Title,
Codes = string.Join(",",
i.Products.Select(l => l.Code).Distinct())
}).SingleOrDefaultAsync(i => i.Id == id);

An anonymous type cannot have multiple properties with the same name

I want to bind gridview through entity framework but it throws error
like-
An anonymous type cannot have multiple properties with the same name Entity Framwrok
Here is my method.
public void UserList(GridView grdUserList)
{
using (TreDbEntities context = new TreDbEntities())
{
var query =( from m in context.aspnet_Membership
from u in context.aspnet_Users
join usr in context.Users
on new { m.UserId, u.UserId }
equals new { usr.MembershipUserID, usr.UserId }
into UserDetails
from usr in UserDetails
select new {
CreationDate = m.CreateDate,
email = m.Email,
UserName = u.LoweredUserName,
Name = usr.FirstName + usr.LastNameLastName,
Active=usr.IsActive
}).ToList();
}
}
It shows error here. usr.UserId.
The direct issue is in the anonymous type new { m.UserId, u.UserId }: the same name twice. You can fix that by giving explicit property names, for example: new { u1 = m.UserId, u2 = u.UserId }.
But then the next issue will be that both anonymous types that define the join will not have the same property names, so the final fix is this:
public void UserList(GridView grdUserList)
{
using (TreDbEntities context = new TreDbEntities())
{
var query =( from m in context.aspnet_Membership
from u in context.aspnet_Users
join usr in context.Users
on new { u1 = m.UserId, u2 = u.UserId }
equals new { u1 = usr.MembershipUserID, u2 = usr.UserId }
into UserDetails
from usr in UserDetails
select new { CreationDate = m.CreateDate,
email = m.Email,
UserName = u.LoweredUserName,
Name = usr.FirstName + " " + usr.LastName,
Active = usr.IsActive
}
).ToList();
}
}
#Gert answer is correct. Just want to show simpler solution - give name only to first UserId property:
on new { MembershipUserID = m.UserId, u.UserId }

LINQ To Entity - Unique Left Outer Join Scenerio

I am trying to write the following Left Outer Join scenerio in Linq to Entity syntax and I can't for the life of me figure out exactly how to pull it off... Here is the working SQL that I am trying to ultimately achieve:
SELECT *
FROM Students s
LEFT JOIN ParentStudents ps ON ps.StudentId = s.StudentId AND ps.ParentId = '6D279F72-2623-459F-B701-5C77C52BA52F'
WHERE s.TenantId = 3 AND s.FamilyId = '28833312-46eb-4a54-9132-8a7c8037cec5'
The part highlighted in Bold is where I fall down ... I want Students to return regardless if there are any ParentStudent records in the database.
Here is my latest LINQ to Entity code that does not work:
public ICollection<ParentStudentListing> GetParentStudents(Guid FamilyId, Guid ParentId)
{
var query = from s in DataContext.Students
from ps in s.ParentStudents.DefaultIfEmpty()
where s.TenantId == CurrentUser.TenantId && s.FamilyId == FamilyId && ps.ParentId == ParentId
select new ParentStudentListing { StudentId = s.StudentId, FirstName = s.FirstName, MiddleName = s.MiddleName, LastName = s.LastName, RelationshipId = ps.RelationshipId, ParentStudentId = ps.ParentStudentId, isAllowedToPickUp = ps.isAllowedToPickUp, isEmergency = ps.isEmergency, isLiveIn = ps.isLiveIn, ParentId = ps.ParentId };
return query.ToList();
}
This code does not bring back students unless there are ParentStudent records in the database which is not the intended result. I want to bring back students regardless if there are ParentStudent records, but if there are ParentStudent records I want those joined up with the Student records...
Thank You!
Here is my first attempt at a LINQ join:
var query = from s in DataContext.Students
join ps in s.ParentStudents on ps.ParentId equals s.ParentId into ps
from ps in ps.DefaultIfEmpty()
where s.TenantId == CurrentUser.TenantId && s.FamilyId == FamilyId
select new ParentStudentListing { StudentId = s.StudentId, FirstName = s.FirstName, MiddleName = s.MiddleName, LastName = s.LastName, RelationshipId = ps.RelationshipId, ParentStudentId = ps.ParentStudentId, isAllowedToPickUp = ps.isAllowedToPickUp, isEmergency = ps.isEmergency, isLiveIn = ps.isLiveIn, ParentId = ps.ParentId };
Referenced from here.
Thanks Joe for the added help .... That didn't quite get me there, but I did finally get it running. Here is the working code:
public ICollection<ParentStudentListing> GetParentStudents(Guid FamilyId, Guid ParentId)
{
var query = from s in DataContext.Students
join ps in DataContext.ParentStudents
on new { s.StudentId, ParentId = ParentId }
equals new { ps.StudentId, ps.ParentId } into ps_join
from ps in ps_join.DefaultIfEmpty()
where s.TenantId == CurrentUser.TenantId && s.FamilyId == FamilyId
select new ParentStudentListing { StudentId = s.StudentId, FirstName = s.FirstName, MiddleName = s.MiddleName, LastName = s.LastName, RelationshipId = ps.RelationshipId, ParentStudentId = ps.ParentStudentId, isAllowedToPickUp = ps.isAllowedToPickUp, isEmergency = ps.isEmergency, isLiveIn = ps.isLiveIn, ParentId = ps.ParentId };
return query.ToList();
}