I use Entity Framework Core 2.1.
I have a scalar function in the database which adds specified number of days.
I created an extension method to execute it:
public static class AdventureWorks2012ContextExt
{
public static DateTime? ExecFn_AddDayPeriod(this AdventureWorks2012Context db, DateTime dateTime, int days, string periodName)
{
var sql = $"set #result = dbo.[fn_AddDayPeriod]('{dateTime.ToString("yyyy-MM-dd HH:mm:ss.fff")}', {days}, '{periodName}')";
var output = new SqlParameter { ParameterName = #"result", DbType = DbType.DateTime, Size = 16, Direction = ParameterDirection.Output };
var result = db.Database.ExecuteSqlCommand(sql, output);
return output.Value as DateTime?;
}
}
I try to use a scalar function in the query (to simplify things I use AdventureWorks2012) as follows:
var persons =
(from p in db.Person
join pa in db.Address on p.BusinessEntityId equals pa.AddressId
where p.ModifiedDate > db.ExecFn_AddDayPeriod(pa.ModifiedDate, 100, "DayPeriod_day")
select p).ToList();
But get an System.InvalidOperationException: 'A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.'
How can I achieve this?
UPDATE:
I managed to do it with the help of Ivan's answer:
var persons =
(from p in db.Person
join bea in db.BusinessEntityAddress on p.BusinessEntityId equals bea.BusinessEntityId
join a in db.Address on bea.AddressId equals a.AddressId
where p.ModifiedDate > AdventureWorks2012ContextFunctions.AddDayPeriod(a.ModifiedDate, 100, "DayPeriod_day")
select p).ToList();
But now I need to update ModifiedDate for filtered persons. So I'm doing like this:
var persons =
(from p in db.Person
join bea in db.BusinessEntityAddress on p.BusinessEntityId equals bea.BusinessEntityId
join a in db.Address on bea.AddressId equals a.AddressId
let date = AdventureWorks2012ContextFunctions.AddDayPeriod(a.ModifiedDate, 100, "DayPeriod_day")
where p.ModifiedDate > date
select new { Person = p, NewDate = date }).ToList();
foreach (var p in persons)
p.Person.ModifiedDate = p.NewDate ?? DateTime.Now;
db.SaveChanges();
But got System.NotSupportedException: 'Specified method is not supported.'
How can I use scalar function in select statement?
I tried to split the query by two parts:
var filteredPersons = // ok
(from p in db.Person
join bea in db.BusinessEntityAddress on p.BusinessEntityId equals bea.BusinessEntityId
join a in db.Address on bea.AddressId equals a.AddressId
where p.ModifiedDate > AdventureWorks2012ContextFunctions.AddDayPeriod(a.ModifiedDate, 100, "DayPeriod_day")
select new { Person = p, a.ModifiedDate }).ToList();
var persons = // here an exception occurs
(from p in filteredPersons
select new { Person = p, NewDate = AdventureWorks2012ContextFunctions.AddDayPeriod(p.ModifiedDate, 100, "DayPeriod_day") }).ToList();
Instead of invoking the function client side (which is this particular case happens as part of the client evaluation of the query filter, while the query reading is still in progress), you can use EF Core Database scalar function mapping so it
can be used in LINQ queries and translated to SQL.
One way to do that is to create a public static method in the derived context class and mark it with DbFunction attribute:
public partial class AdventureWorks2012Context
{
[DbFunction("fn_AddDayPeriod")]
public static DateTime? AddDayPeriod(DateTime dateTime, int days, string periodName) => throw new NotSupportedException();
}
and use
where p.ModifiedDate > AdventureWorks2012Context.AddDayPeriod(pa.ModifiedDate, 100, "DayPeriod_day")
Another way is to create a public static method in another class
public static class AdventureWorks2012DbFunctions
{
[DbFunction("fn_AddDayPeriod")]
public static DateTime? AddDayPeriod(DateTime dateTime, int days, string periodName) => throw new NotSupportedException();
}
but then you'll need to register it with fluent API (which happens automatically for methods defined inside the context derived class):
modelBuilder
.HasDbFunction(() => AdventureWorks2012DbFunctions.AddDayPeriod(default(DateTime), default(int), default(string)));
The usage is the same:
where p.ModifiedDate > AdventureWorksDbFunctions.AddDayPeriod(pa.ModifiedDate, 100, "DayPeriod_day")
Related
I am trying to do left outer join in LINQ for two vars but on selecting required coloumns, I get Object reference not set to an instance of an object error where I want Nullable decimal.
var FLS = (from ee in SumTillFYEnd
join es in SumTillFYStart on ee.Account equals es.Account into temp
from t in temp.DefaultIfEmpty()
select new
{
Account = ee.Account, // As of here it works
BeginDr = (t.DrStartCF == 0) ? (decimal?) null : t.DrStartCF // Here I get error Object reference not set to an instance of an object.
});
Some times SumTillFYEnd and some times SumTillFYStart becomes null. I want to join should work with default values, in case any one or both is null.
The problem is attempting to cast null to decimal?. You cannot ever directly cast null to another type, nullable or not. That will always cause a NullReferenceException. What you want instead is default. In other words, replace:
(decimal?)null
With
default(decimal?)
I solved this using a default class.
The reason I am seeing is that decimal can not be null so it either needs to set for a default value either 0 or decimal.MinValue
So, you require to have default class for SumTillFYStart like
var defaultSumTillFYStart = new SumTillFYStart { Account = string.Empty, DrStartCF =0};
With above in context, then in your piece of code replace
from t in temp.DefaultIfEmpty()
with this
from t in temp.DefaultIfEmpty(defaultSumTillFYStart)
I have a linqPad working written below but for different subset; I think it will help somebody:
void Main()
{
List<Debtor> debtors = new List<Debtor>();
List<SecurityHolding> holdings = new List<SecurityHolding>();
//Initialize Debtor
debtors.Add(new Debtor(){
AccountId = "J1",
OutstandingValue = 501.95M
});
debtors.Add(new Debtor(){
AccountId = "J2",
OutstandingValue = 75.68M
});
debtors.Add(new Debtor(){
AccountId = "J3",
OutstandingValue = 100.01M
});
//Initialize Security Holding
holdings.Add(new SecurityHolding(){
AccountId = "J2",
SecurityHoldingValue = 100M
});
holdings.Add(new SecurityHolding(){
AccountId = "J3",
SecurityHoldingValue = 200M
});
var defaultHolding = new SecurityHolding { AccountId= string.Empty, SecurityHoldingValue = 0};
var result = (from d in debtors
join p in holdings
on d.AccountId equals p.AccountId into temp
from t in temp.DefaultIfEmpty(defaultHolding)
select new
{
AccountId = d.AccountId,
OutstandingValue = d.OutstandingValue,
HoldingValue = (decimal?)t.SecurityHoldingValue
});
result.Dump();
}
// Define other methods and classes here
public class Debtor
{
public string AccountId {get;set;}
public decimal OutstandingValue {get;set;}
}
public class SecurityHolding
{
public string AccountId {get;set;}
public decimal SecurityHoldingValue {get;set;}
}
Here the output:
I am trying to get a ; demlimited string of all the relatives that a person has.
Four tables are involved:
USERTAB
PERSON
PERSON_RELATION
RELATION_TAB
Query
from u in USERTAB
select new
{
Person = from p in PERSON where p.USERID == u.USERID
select new
{
PNo = p.NO,
Name = p.NAME
Relatives = (from r in PERSON_RELATION where r.PSEQ == p.PSEQ select new
{
Description = (from rel in RELATION_TYPE where rel.TYPE_SEQ == r.TYPE_SEQ select rel.DESCRIPTION).ToArray() //(or also tried .ToString())
})
}
}
I'd like the Description field to be a ";" delimited list of all the relatives a user (person) has.
Using a ToString on my Relatives object it only fails runtime with. LINQ to Entities does not recognize the method 'System.String ToString()' method
Example: Description = "Father, Brother, Cousin"
You need to be clear about what parts of your query get translated to SQL to run on the server, and what parts run in your local application. The key is to construct a simple query to retrieve all the data, then use .AsEnumerable() to ensure that the remaining transformations don't get translated to SQL, and finally transform the data into a form useful for you. Something like
var query =
from u in USERTAB
select new
{
Person =
from p in PERSON
where p.USERID == u.USERID
select new
{
PNo = p.NO,
Name = p.NAME
Relatives =
from r in PERSON_RELATION
where r.PSEQ == p.PSEQ
select new
{
Description =
from rel in RELATION_TYPE
where rel.TYPE_SEQ == r.TYPE_SEQ
select pos.DESCRIPTION
}
}
}
};
var enumerable =
from u in query.AsEnumerable()
select new
{
Person =
from p in u.Person
select new
{
PNo = p.PNo,
Name = p.Name
Relatives =
string.Join(", ",
from r in p.Relatives
from d in r.Description
select d.Description)
}
}
};
should do the trick.
This should work: write an extension method for strings of array, like so:
public static ToCsv(this string[] strings)
{
return String.Join("," strings);
}
Then just tack .ToCsv() at the end of your .ToArray() call above, and it should do the trick!
I have a linq query which returns results ordered by first letter. Is there a way to return the first letter before the group ordered by that letter? For instance;
**A**
Acountants
Apothecary
**B**
Basketball
Biscuits
and so on. I tried grouping my results like this;
var companyquery = (from c in db.Categories
group c by c.Name.Substring(0, 1)
into cgroup
select new
{
FirstLetter = cgroup.Key,
Names = cgroup
}).OrderBy(letter => letter.FirstLetter);
return View(companyquery);
but got error:
"The model item passed into the dictionary is of type 'System.Data.Entity.Infrastructure.DbQuery1[<>f__AnonymousType31[System.String]]', but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable`1[NewAtAClick.Models.Category]'.'
So now I'm using orderby instead of group _ into _ . Here is my query;
var companyquery = (from c in db.Categories
orderby c.Name.Substring(0, 1)
select c);
This returns them in alphebetical order. I tried saying
letter1 = companyquery.ToString().SubString(0,1)
then
return view(letter1 + companyquery.ToList());
But nothing.
Thanks in advance!
The error says it all. Your view expects a model of type IEnumerable<Category> but you passed in something else (in this case because you are creating a new anonymous type via the call to select new { ... }).
Instead you could declare a new type:
public class AlphabeticalMapping<T> {
public char FirstLetter { get; set; }
public List<T> Items { get; set; }
}
And change your query to return:
var companyquery = (from c in db.Categories
group c by c.Name.Substring(0, 1)
into cgroup
select new AlphabeticalMapping<Category>() {
FirstLetter = cgroup.Key,
Items= cgroup.ToList()
}).OrderBy(mapping => mapping.FirstLetter);
And change your view to expect IEnumerable<AlphabeticalMapping<Category>>
What happens if you try this?
var companyquery = (from c in db.Categories
group c by c.Name.Substring(0, 1)
into cgroup
select new,
{
FirstLetter = cgroup.Key,
Names = cgroup
})
.OrderBy(letter => letter.FirstLetter)
.ToDictionary(k => k.FirstLetter, e => e.Names);
I have the below SQL which works just fine:
SELECT Message, CreateDate, AccountId, AlertTypeId
FROM dbo.Alerts
UNION
SELECT TOP (100) PERCENT Status, CreateDate, AccountId,
(SELECT 10 AS Expr1) AS AlertTypeId
FROM dbo.StatusUpdates
WHERE AccountId = PassedInParameter
ORDER BY CreateDate DESC
I am trying to convert it to LINQ, which doesn't work just fine :) Obviously, there is a lot wrong here - it is just a rough start. It does not account for the above temp column or the order by condition and the generics / return type ambiguity is my attempt to make sense of the two different return types:
public List<T> GetSomething<T>(Int32 accountId)
{
List<T> result;
using (DataContext dc = _conn.GetContext())
{
IEnumerable<Alert> alerts = (from a in dc.Alerts
where a.AccountId == accountId
select a);
IEnumerable<StatusUpdate> updates = (from s in dc.StatusUpdates
where s.AccountId == accountId
select s);
IEnumerable<T> obj = alerts.Union(updates);
result = obj.ToList();
}
return result;
}
The problems I am having are:
1) I am dealing with two different types (Alerts and StatusUpdate) in my selects and
I am not sure how to combine them (or what type to return). I am guessing this might
be solved with generics?
2) In my SQL, I have this code: (SELECT 10 AS Expr1) AS AlertTypeId which adds the value ten to the temp column AlertTypeId (allowing the union to match it to Alert's real column AlertTypeId). How are temp columns such as this accomplished in LINQ / how do I do this?
Thanks for your help.
EDIT---------------------------------EDIT------------------------------------------EDIT
OK, I am a little further along. Below is what I have currently. You will notice I added some logic to return the updates for friend relations. I also made this a generic method of type IList given that alerts and updates have to be generic to agree. I pass in StatusUpdate in the calling method (further down below).
public IList GetUpdatesByAccountId<T>(Int32 accountId)
{
List<Friend> friends = _friendRepository.GetFriendsByAccountId(accountId);
using (DataContext dc = _conn.GetContext())
{
// Get all the account ids related to this user
var friendAccountIds =
friends.Select(friend => friend.MyFriendsAccountId).Distinct();
friendAccountIds = friendAccountIds.Concat(new[] { accountId });
var updates =
dc.StatusUpdates.Where(s => s.AccountId.HasValue && friendAccountIds.Contains(s.AccountId.Value)).Select(
s => new { Alert = (Alert)null, StatusUpdate = s});
var alerts =
dc.Alerts.Where(a => a.AccountId == accountId).Select(
a => new {Alert = a, StatusUpdate = (StatusUpdate) null});
var obj = updates.Union(alerts).Take(100);
return obj.OrderByDescending(su => su.StatusUpdate.CreateDate).ToList();
}
}
And, the calling method:
protected void LoadStatus()
{
repStatusUpdates.DataSource = _statusRepository
.GetUpdatesByAccountId<StatusUpdate>(_userSession.CurrentUser.AccountId);
repStatusUpdates.DataBind();
}
AND here are the interfaces to the repositories I am using to access my Alert and StatusUpdate tables via LINQ:
public interface IAlertRepository
{
List<Alert> GetAlertsByAccountId(Int32 accountId);
void SaveAlert(Alert alert);
void DeleteAlert(Alert alert);
}
public interface IStatusUpdateRepository
{
StatusUpdate GetStatusUpdateById(Int32 statusUpdateId);
List<StatusUpdate> GetStatusUpdatesByAccountId(Int32 accountId);
List<StatusUpdate> GetFriendStatusUpdatesByAccountId(Int32 accountId, Boolean addPassedInAccount);
void SaveStatusUpdate(StatusUpdate statusUpdate);
List<StatusUpdate> GetTopNStatusUpdatesByAccountId(Int32 accountId, Int32 number);
List<StatusUpdate> GetTopNFriendStatusUpdatesByAccountId(Int32 accountId, Int32 number, Boolean addPassedInAccount);
}
Current Problems:
1) When I compile this code, I get this strange error:
Unable to cast object of type
'System.Data.Linq.SqlClient.SqlNew' to
type
'System.Data.Linq.SqlClient.SqlValue'.
The only reading I can find on it is this link although there isn't a clear solution there (at least that I can tell). However, if the above LINQ code does not look good to you, maybe whatever you suggest will cause this error to disappear.
2) The above code is still not accounting for this line from the original SQL:
(SELECT 10 AS Expr1) AS AlertTypeId
but this is minor.
Thanks again for the help.
Try this (i converted the StatusUpdate to an alert, if this isn't acceptable, you're going to have to either convert the Alert to a StatusUpdate, or create a new class):
var alerts = (from a in dc.Alerts
where a.AccountId == accountId
select a);
var updates = (from s in dc.StatusUpdates
where s.AccountId == accountId
select s)
.OrderByDescending( x => x.CreateDate)
.Take(100)
.Select( x => new Alert
{
Message = x.Percent.ToString(),
CreateDate = x.CreateDate,
AccountId = x.AccountId,
AlertTypeId = 10 // Is this right?
}
);
var obj = alerts.Union(updates);
result = obj.ToList();
The reason I do the Select last is so that you don't have to construct a new alert for all the results your are not using.
This will give you a list of Alerts.
Using a generic in this situation is sort of hard to pull off. For instance, you can't do this:
IQueryable alerts = (from a in _alerts
where a.AccountId == accountId
select a);
Because that implicitly converts a to type T. Even if you try to limit what T implements or inherits from:
public List<T> GetSomething<T>(Int32 accountId) where T : IAlert// Interface that both StatusUpdates and IAlert implement
public List<T> GetSomething<T>(Int32 accountId) where T : Alert
public List<T> GetSomething<T>(Int32 accountId) where T : AlertBase // Base class for both Status and Alert
You'll still run into problems because there is no way to statically know exactly what type T is, so you cannot know if it can be converted from Alert and StatusUpdate.
An alternative is to explicitly use IAlert as your return type:
public List<IAlert> GetSomething(Int32 accountId)
With IAlert:
public interface IAlert
{
int AccountId { get; set; }
int AlertTypeId { get; set; }
DateTime CreateDate { get; set; }
string Message { get; set; }
}
If you have have both Alert and StatusUpdate implement IAlert, you could rewrite it as so:
IQueryable<IAlert> alerts = (from a in dc.Alerts
where a.AccountId == accountId
select a);
IQueryable<IAlert> updates = (from s in dc.StatusUpdates
where s.AccountId == accountId
select s)
.OrderByDescending( x => x.CreateDate)
.Take(100);
var obj = alerts.Union(updates);
result = obj.ToList();
This is the route I would take instead of passing in some unknown type and trying to limit what it implements or inherits, because casting to that type might still be invalid.
You can only take unions of sequences of equal types. You need to convert alerts and updates to sequences of a common type, then take the union. You can do so using anonymous types. Especially useful if the types don't have anything in common.
//this is a hack and probably not what you would want to use.
var alerts =
from a in dc.Alerts
where a.AccountId == accountId
select new { Alert = a, StatusUpdate = (StatusUpdate)null };
var updates =
from s in dc.StatusUpdates
where s.AccountId == accountId
select new { Alert = (Alert)null, StatusUpdate = s };
//both are sequences of anonymous type with properties:
// Alert (of type Alert)
// StatusUpdate (of type StatusUpdate)
var obj = alerts.Union(updates);
If you have fields in common, you'd still use anonymous types except you'd include the known fields.
var alerts =
from a in dc.Alerts
where a.AccountId == accountId
select new
{
a.Message, //assuming is a string
Status = (string)null,
a.CreateDate,
a.AccountId,
a.AlertTypeId //assuming is an int
};
var updates =
(from s in dc.StatusUpdates
where s.AccountId == accountId
select new
{
Message = (string)null,
s.Status, //assuming is a string
s.CreateDate,
s.AccountId,
AlertTypeId = 10 //this should handle the "10 AS AlertTypeId" part
}).OrderByDescending(s => s.CreateDate);
var obj = alerts.Union(updates);
The key is that both anonymous types has the same exact properties of the same exact types. Then you can take the union between them both.
Linq to SQL, in the dbml designer (or otherwise)
I have 3 tables:
Orders, Deliveries and EmailTemplates.
Orders have many Deliveries, and Orders and Deliveries have a status (int) field.
EmailTemplates have a status they apply to and a bool IsForDeliveries field.
I have Linq to sql associations for Order->EmailTemplate on order.status == emailTemplate.status, but I want to add a condition on the association such that emailTemplate.IsForDeliveries == false. Is this possible, or do I just have to remember to check this condition whenever I access order.EmailTemplates?
Edit
AssociateWith is problematic because I also need the counterpart Delivery<->EmailTemplate association which only shows templates with e.is_for_delivery == true.
Adding a property to the class is problematic because I'd like this to be translatable to SQL.
You can use DataLoadOptions.AssociateWith() for this or just create an additional property on the class. Since the Order class is a partial, you could just create a method in a partial beside it adding this:
public IEnumerable<EmailTemplate> DeliveryEmailTemplates {
get { return EmailTemplates.Where(e => e.IsForDeliveries == false); }
}
or alternatively, when creating the datacontext:
var dc = new DataContext();
var dlo = new DataLoadOptions();
dlo.AssociateWith<EmailTemplates>(o => o.EmailTemplates.Where(e => e.IsForDeliveries == false));
dc.LoadOptions = dlo;
var orders = from o in DB.Orders
where o.Id == 5
select o;
foreach(var o in orders) {
//o.EmailTemplates will contain only IsForDeliveries==false...
}
If you fetch your DataContext in a static way you can add this every time, for example I add a method in a partial class of the DataContext called .New:
public static DataContext New {
get {
var dc = new DataContext(MyConnectionString);
var dlo = new DataLoadOptions();
dlo.AssociateWith<EmailTemplates>(o => o.EmailTemplates.Where(e => e.IsForDeliveries == false));
dc.LoadOptions = dlo;
return dc;
}
}
Then use:
var DB = DataContext.New;
var orders = from o in DB.Orders
where o.Id == 5
select o;