Entity Framework Core Select Outer Join - entity-framework

is there an easy way to include a nullable navigation inside a select expression for EF Core?
My model looks like this
public class RootVO
{
[Key]
public int Id { get; set; }
[Required]
[StringLength(200)]
public string Description { get; set; }
public int? RelationId { get; set; }
[ForeignKey(nameof(RelationId))]
public RelationVO Relation { get; set; }
}
public class RelationVO
{
[Key]
public int Id { get; set; }
[Required]
[StringLength(200)]
public string Property1 { get; set; }
[Required]
[StringLength(200)]
public string Property2 { get; set; }
public ICollection<RootVO> RootRelations { get; set; }
}
When I load the data I just want to select certain kind of properties. Currently my expression looks like this:
Expression<Func<RootVO, RootVO>> selectExpr = m => new RootVO
{
Id = m.Id,
Description = m.Description,
Relation = m.Relation != null ? new RelationVO
{
Id = m.Relation.Id,
Property1 = m.Relation.Property1
} : null
};
var result = context.Roots.Select(selectExpr).ToList();
Is there an easier way to handle the relation select?
Edit
Maybe some background here will help:
I have a huge object with a lot of columns and relations, some with inner, some with outer joins. This query gets accessed by a datagrid on UI which can have dynamic columns depending on the user selection. To increase the performance I've written a class that will build the select expression dynamicly depending on the selected columns. For now it is working, but I'm having trouble when an outer join is null due to null-reference excepction.
The debug view on the expression could look like this:
.New IVA.Core.Data.Models.StockMovementLogVO(){
SequenceNo = $m.SequenceNo,
PostingPeriodId = $m.PostingPeriodId,
TransactionDate = $m.TransactionDate,
FinancialYear = $m.FinancialYear,
FinancialYearPeriod = $m.FinancialYearPeriod,
VoucherDate = $m.VoucherDate,
ItemQuantity = $m.ItemQuantity,
BuCode = $m.BuCode,
LocationStructure = .New IVA.Core.Data.Models.LocationStructureVO(){
Id = ($m.LocationStructure).Id,
Description = ($m.LocationStructure).Description
},
BookingType = .New IVA.Core.Data.Models.BookingTypeVO(){
Id = ($m.BookingType).Id,
Description = ($m.BookingType).Description
},
PartnerStockLocationType = .New IVA.Core.Data.Models.StockLocationTypeVO(){
Id = ($m.PartnerStockLocationType).Id,
Description = ($m.PartnerStockLocationType).Description
},
StockLocationType = .New IVA.Core.Data.Models.StockLocationTypeVO(){
Id = ($m.StockLocationType).Id,
Description = ($m.StockLocationType).Description
}
}
StockLocationType and PartnerStockLocationType are outer joins and if those are null the query fails to execute.

I've now changed my expression builder that it will take care of the outer joins by including a null reference check. The expression now looks like this:
.New IVA.Core.Data.Models.StockMovementLogVO(){
SequenceNo = $m.SequenceNo,
PostingPeriodId = $m.PostingPeriodId,
TransactionDate = $m.TransactionDate,
FinancialYear = $m.FinancialYear,
FinancialYearPeriod = $m.FinancialYearPeriod,
VoucherDate = $m.VoucherDate,
ItemQuantity = $m.ItemQuantity,
BuCode = $m.BuCode,
LocationStructure = .New IVA.Core.Data.Models.LocationStructureVO(){
Id = ($m.LocationStructure).Id,
Description = ($m.LocationStructure).Description
},
BookingType = .New IVA.Core.Data.Models.BookingTypeVO(){
Id = ($m.BookingType).Id,
Description = ($m.BookingType).Description
},
PartnerStockLocationType = .If ($m.PartnerStockLocationType != null) {
.New IVA.Core.Data.Models.StockLocationTypeVO(){
Id = ($m.PartnerStockLocationType).Id,
Description = ($m.PartnerStockLocationType).Description
}
} .Else {
null
},
StockLocationType = .If ($m.StockLocationType != null) {
.New IVA.Core.Data.Models.StockLocationTypeVO(){
Id = ($m.StockLocationType).Id,
Description = ($m.StockLocationType).Description
}
} .Else {
null
}
}
Edit
If anyone is interessted how it looks, I've created a repository where I use the class.
https://github.com/NQuirmbach/DynamicQueryBuilder

Related

How to write an audit log entry per changed property with Audit.NET EntityFramework.Core

I'm trying to get the Audit:NET EntityFramework.Core extension to write an AuditLog entry per changed property.
For this purpose I've overidden the EntityFrameworkDataProvider.InsertEvent with a custom DataProvider.
The problem is, using DbContextHelper.Core.CreateAuditEvent to create a new EntityFrameworkEvent returns null.
The reason seems to be, at this point in the code execution DbContextHelper.GetModifiedEntries determines all EF Entries have State.Unmodified, even if they are clearly included in the EventEntry changes.
I'm trying to circumvent CreateAuditEvent by manually creating the contents is impossible due to private/internal properties.
Maybe there is an alternative solution to this problem I'm not seeing, i'm open to all suggestions.
Audit entity class
public class AuditLog
{
public int Id { get; set; }
public string Description { get; set; }
public string OldValue { get; set; }
public string NewValue { get; set; }
public string PropertyName { get; set; }
public DateTime AuditDateTime { get; set; }
public Guid? AuditIssuerUserId { get; set; }
public string AuditAction { get; set; }
public string TableName { get; set; }
public int TablePK { get; set; }
}
Startup configuration
Audit.Core.Configuration.Setup()
.UseCustomProvider(new CustomEntityFrameworkDataProvider(x => x
.AuditEntityAction<AuditLog>((ev, ent, auditEntity) =>
{
auditEntity.AuditDateTime = DateTime.Now;
auditEntity.AuditAction = ent.Action;
foreach(var change in ent.Changes)
{
auditEntity.OldValue = change.OriginalValue.ToString();
auditEntity.NewValue = change.NewValue.ToString();
auditEntity.PropertyName = change.ColumnName;
}
}
Custom data provider class
public class CustomEntityFrameworkDataProvider : EntityFrameworkDataProvider
{
public override object InsertEvent(AuditEvent auditEvent)
{
var auditEventEf = auditEvent as AuditEventEntityFramework;
if (auditEventEf == null)
return null;
object result = null;
foreach (var entry in auditEventEf.EntityFrameworkEvent.Entries)
{
if (entry.Changes == null || entry.Changes.Count == 0)
continue;
foreach (var change in entry.Changes)
{
var contextHelper = new DbContextHelper();
var newEfEvent = contextHelper.CreateAuditEvent((IAuditDbContext)auditEventEf.EntityFrameworkEvent.GetDbContext());
if (newEfEvent == null)
continue;
newEfEvent.Entries = new List<EventEntry>() { entry };
entry.Changes = new List<EventEntryChange> { change };
auditEventEf.EntityFrameworkEvent = newEfEvent;
result = base.InsertEvent(auditEvent);
}
}
return result;
}
}
Check my answer here https://github.com/thepirat000/Audit.NET/issues/323#issuecomment-673007204
You don't need to call CreateAuditEvent() you should be able to iterate over the Changes list on the original event and call base.InsertEvent() for each change, like this:
public override object InsertEvent(AuditEvent auditEvent)
{
var auditEventEf = auditEvent as AuditEventEntityFramework;
if (auditEventEf == null)
return null;
object result = null;
foreach (var entry in auditEventEf.EntityFrameworkEvent.Entries)
{
if (entry.Changes == null || entry.Changes.Count == 0)
continue;
// Call base.InsertEvent for each change
var originalChanges = entry.Changes;
foreach (var change in originalChanges)
{
entry.Changes = new List<EventEntryChange>() { change };
result = base.InsertEvent(auditEvent);
}
entry.Changes = originalChanges;
}
return result;
}
Notes:
This could impact performance, since it will trigger an insert to the database for each column change.
If you plan to use async calls to DbContext.SaveChangesAsync, you should also implement the InsertEventAsync method on your CustomDataProvider
The Changes property is only available for Updates, so if you also want to audit Inserts and Deletes, you'll need to add the logic to get the column values from the ColumnValues property on the event

Cannot insert explicit value for identity column while updating entity

I am using ASP.NET Boilerplate template.
I want to update Details table, which contains more than one item. If an item exists, it must update, otherwise a new one must be added and all other entries relating to Master primary key in Details table must be deleted. But it is showing an error:
Cannot insert explicit value for identity column in table
'SemesterDetails' when IDENTITY_INSERT is set to OFF
This is the Master table:
public class StudentDegreeCore : Entity<int>
{
[StringLength(150)]
[Required(ErrorMessage = "Enter Degree College ")]
public string DegreeCollege { get; set; }
[Required()]
public string CollegeID { get; set; }
[StringLength(7, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 7)]
[Required(ErrorMessage = "Enter 10th Pass Year")]
public string CommencementYear { get; set; }
public List<StudentSemesterCore> SemesterDetails { get; set; }
}
This is the Details table, represented by the StudentSemesterCore class:
public class StudentSemesterCore
{
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[StringLength(150)]
[Required(ErrorMessage = "Enter Year/Semester")]
public string YearOrSemester { get; set; }
[Required()]
public virtual int StudentDegreeID { get; set; }
[ForeignKey("StudentDegreeID")]
public virtual StudentDegreeCore StudentDegreeCore { get; set; }
[StringLength(4, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 4)]
[Required(ErrorMessage = "Enter Semester Status")]
public string Status { get; set; }
[DisplayName("% of Marks")]
[RegularExpression(#"\d+(\.\d{1,2})?", ErrorMessage = "Numbers With Two decimal Place Allowed")]
public decimal MarkPercentage { get; set; }
}
This is the Update code:
_studentdegreeRepository.Update(st);
CurrentUnitOfWork.SaveChanges();
It shows an error when SaveChanges is called. Actually, I want to update the details if the same value exists, otherwise add new one and all other data relating to the same StudentDegreeID must be removed.
StudentSemesterCore is not derived from Entity.
You don't need to put Id property in StudentSemesterCore. Remove it.
Add StudentDegreeCoreId to StudentSemesterCore as foreign key reference.
I tried this
public override async Task<StudentDegreeDto> Create(StudentDegreeCreateDto input)
{
//CheckCreatePermission();
StudentDegreeCore st = new StudentDegreeCore();
try
{
StudentDegreeCore core = new StudentDegreeCore()
{
Id = input.Id,
Address1 = input.Address1,
Address2 = input.Address2,
City = input.City,
CollegeID = input.CollegeID,
CommencementYear = input.CommencementYear,
CompletionYear = input.CompletionYear,
CurrentYear = input.CurrentYear,
DegreeCollege = input.DegreeCollege,
DegreeId = input.DegreeId,
OverallPercent = input.OverallPercent,
PinCode = input.PinCode,
PostBox = input.PostBox,
State = input.State,
StreamId = input.StreamId,
UserId = input.UserId
};
core.SemesterDetails = new List<StudentSemesterCore>();
foreach (var items in input.SemesterDetails)
{
core.SemesterDetails.Add(new StudentSemesterCore()
{
GPA = items.GPA,
MarkPercentage = items.MarkPercentage,
Status = items.Status,
UserId = items.UserId ,
Id = items.Id,
StudentDegreeID = items.StudentDegreeID ,
YearOrSemester = items.YearOrSemester,
LastModificationTime = DateTime.Now,
CreationTime = DateTime.Now
});
}
var student = core; //ObjectMapper.Map<StudentDegreeCore>(input);
long uid = (AbpSession.UserId == null) ? 0 : Convert.ToInt64(AbpSession.UserId);
st = _studentRepository.Get(student.Id);
if (st != null && st.Id > 0)
{
st.DegreeCollege = student.DegreeCollege;
st.CollegeID = student.CollegeID;
st.CommencementYear = student.CommencementYear;
st.CompletionYear = student.CompletionYear;
st.LastModificationId = Convert.ToInt32(AbpSession.UserId);
st.LastModificationTime = DateTime.Now;
st.StreamId = student.StreamId;
st.DegreeId = student.DegreeId;
st.CurrentYear = student.CurrentYear;
st.OverallPercent = student.OverallPercent;
st.PinCode = student.PinCode;
st.PostBox = student.PostBox;
st.State = student.State;
st.Address1 = student.Address1;
st.Address2 = student.Address2;
st.City = student.City;
st.SemesterDetails = new List<StudentSemesterCore>();
//st.SemesterDetails = student.SemesterDetails;
_studentRepository.Update(st);
foreach (var items in student.SemesterDetails)
{
_studentSemesterRepository.InsertOrUpdate(items);
}
//_studentRepository.Update(st);
CurrentUnitOfWork.SaveChanges();
}
else
{
student.UserId = Convert.ToInt32(AbpSession.UserId);
student.CreationId = Convert.ToInt32(AbpSession.UserId);
_studentRepository.Insert(student);
CurrentUnitOfWork.SaveChanges();
}
}
catch (Exception ex)
{
}
StudentDegreeDto studentDegreeDto = new StudentDegreeDto()
{
Id = input.Id,
Address1 = input.Address1,
Address2 = input.Address2,
City = input.City,
CollegeID = input.CollegeID,
CommencementYear = input.CommencementYear,
CompletionYear = input.CompletionYear,
CurrentYear = input.CurrentYear,
DegreeCollege = input.DegreeCollege,
DegreeId = input.DegreeId,
OverallPercent = input.OverallPercent,
PinCode = input.PinCode,
PostBox = input.PostBox,
State = input.State,
StreamId = input.StreamId,
UserId = input.UserId
};
studentDegreeDto.SemesterDetails = new List<StudentSemesterDto>();
foreach (var items in input.SemesterDetails)
{
studentDegreeDto.SemesterDetails.Add(new StudentSemesterDto()
{
GPA = items.GPA,
MarkPercentage = items.MarkPercentage,
Status = items.Status,
YearOrSemester = items.YearOrSemester,
LastModificationTime = DateTime.Now,
CreationTime = DateTime.Now
});
}
return studentDegreeDto;
}

Project into a (list of) concrete object instead of an (list of) anonymous object

The problem simplified is as follows: in Entity Framework i am doing a join involving 3 tables, and returning the joined result set, which involves (some) fields from the 3 tables.
var query = (
from t1 in dbCtx.TB_Entity1
from t2 in dbCtx.TB_Entity2
.Where(p => p.someCol == t1.someCol && t.IsActive == true)
.DefaultIfEmpty() //LEFT JOIN
from t3 in dbCtx.TB_Entity3
.Where(q => q.someCol == t2.someCol)
where t1.IsLatest == true
&& (t1.istatus == 2
|| t1.istatus == 3
)
select new {
t1.col100,
t1.col101,
t2.col200,
t2.col201,
t3.col300,
t3.col301
}).OrderByDescending(t1 => t1.ID);
var anonObjList = query.ToList();
Thus, at the end of the query I write a projection to select the fields i want.
Finally i run the query with .ToList() and get a list of Anonymous objects.
How do i modify the query to project into a List of MyConcreteClass
i.e. i want to be able to write something similar to
List<MyConcreteClass> myObjList = query.ToList();
You may assume my concrete class looks like
public class MyConcreteClass
{
public string Col100 { get; set; }
public string Col101 { get; set; }
public string Col200 { get; set; }
public string Col201 { get; set; }
public string Col300 { get; set; }
public string Col301 { get; set; }
}
You just use the object initializer syntax:
new MyConcreteClass
{
Col100 = t1.col100,
Col101 = t1.col101,
Col200 = t2.col200,
Col201 = t2.col201,
Col300 = t3.col300,
Col301 = t3.col301
}

IQueryable.Select to a List type sub POCO

I have an Entity Framework model in which there is a "Customers" and a "CustomerPhones" table. A customer can have multiple phone numbers so the "Customer" entity has a collection of "Phone". I can query the model with no problem :
using (CustomerEntities context = new CustomerEntities())
{
Customer customer = context.Customers.FirstOrDefault();
CustomerPhone phone = customer.Phones.FirstOrDefault();
MessageBox.Show(customer.Name + " " + phone.Number);
}
The model is too complex for what I need to do (even though my example is basic) so I'm trying to boil it down to simpler POCOs. Here are the 2 simple classes :
public class SimplePhone
{
public int Id { get; set; }
public string Number { get; set; }
}
public class SimpleCustomer
{
public int Id { get; set; }
public string Name { get; set; }
//Phones is a list because a single Customer can have multiple phone numbers
public List<SimplePhone> Phones { get; set; }
}
I can populate the simple properties of the object using the "Select" method of "IQueryable" :
using (CustomerEntities context = new CustomerEntities())
{
IQueryable<SimpleCustomer> customers = context.Customers.Select(
c => new SimpleCustomer
{
Id = c.Id,
Name = c.Name
}
);
SimpleCustomer customer = customers.FirstOrDefault();
MessageBox.Show(customer.Name);
}
So my question is pretty simple : how can I populate the "Phones" property which is a list?
using (CustomerEntities context = new CustomerEntities())
{
IQueryable<SimpleCustomer> customers = context.Customers.Select(
c => new SimpleCustomer
{
Id = c.Id,
Name = c.Name
Phones = ///????
}
);
SimpleCustomer customer = customers.FirstOrDefault();
SimplePhone phone = customer.Phones.FirstOrDefault();
MessageBox.Show(customer.Name + " " + phone.Number);
}
Let me know if I'm unclear and/or you need more details.
Thanks!
I'm not sure if there isn't something more to your question, but as far as I understand, you can just call ToList and it will be materialized as a list:
IQueryable<SimpleCustomer> customers =
context.Customers.Select(c => new SimpleCustomer
{
Id = c.Id,
Name = c.Name,
Phones = c.Phones.Select(p => new SimplePhone
{
Id = p.Id, // Unless you want the custom Id, i.e. c.Id
Number = p.Number
}).ToList();
});

Converting ESQL to LINQ to Entities. Sort by related entities

I am using EF + RIA and unfortunately meet some problems with sorting by related entities.
For such purpose there is ESQL query that I implemented (found only this solution):
var queryESQL = string.Format(
#" select VALUE ent from SomeEntities as ent
join Attributes as ea ON ea.EntityId = ent.Id
where ea.AttributeTypeId = #typeId
order by ea.{0} {1}", columnName, descending ? "desc" : "asc");
var query = ObjectContext.CreateQuery<SomeEntity>(queryESQL, new ObjectParameter("typeId", attributeTypeId));
Tables have following structure:
<Attribute>:
int Id;
decimal DecimalColumn;
string StringColumn;
int EntityId;
int AttributeTypeId;
<SomeEntity>:
int Id;
string Name;
Is there any way to rewrite this stuff(sorting), using LINQ to Entities approach?
Here's my attempt, I can't guarantee it will work. I need to think more on how to get a dynamic column name, I'm not sure on that one. EDIT: you can use a string for the order column.
int typeId = 1115;
bool orderAscending = false;
string columnName = "StringColumn";
var query = from ent in SomeEntities
join ea in Attributes on ea.EntityId = ent.Id
where ea.AttributeTypeId = typeId;
if(orderAscending)
{
query = query.OrderBy(ea => columnName).Select(ea => ea.Value);
}
else
{
query = query.OrderByDescending(ea => columnName).Select(ea => ea.Value);
}
var results = query.ToList(); // call toList or enumerate to execute the query, since LINQ has deferred execution.
EDIT: I think that ordering after the select stops is from ordering by. I moved the select statement to after the order by. I also added the "query =", but I'm not sure if that is needed. I don't have a way to test this at the moment.
EDIT 3: I fired up LINQPad today and made a few tweaks to what I had before. I modeled your data in a Code-first approach to using EF and it should be close to what you have.
This approach works better if you're just trying to get a list of Attributes (which you aren't). To get around that I added an Entity property to the MyAttribute class.
This code works in LINQPAD.
void Main()
{
// add test entities as needed. I'm assuming you have an Attibutes collection on your Entity based on your tables.
List<MyEntity> SomeEntities = new List<MyEntity>();
MyEntity e1 = new MyEntity();
MyAttribute a1 = new MyAttribute(){ StringColumn="One", DecimalColumn=25.6M, Id=1, EntityId=1, AttributeTypeId = 1, Entity=e1 };
e1.Attributes.Add(a1);
e1.Id = 1;
e1.Name= "E1";
SomeEntities.Add(e1);
MyEntity e2 = new MyEntity();
MyAttribute a2 = new MyAttribute(){ StringColumn="Two", DecimalColumn=198.7M, Id=2, EntityId=2, AttributeTypeId = 1, Entity=e2 };
e2.Attributes.Add(a2);
e2.Id = 2;
e2.Name = "E2";
SomeEntities.Add(e2);
MyEntity e3 = new MyEntity();
MyAttribute a3 = new MyAttribute(){ StringColumn="Three", DecimalColumn=65.9M, Id=3, EntityId=3, AttributeTypeId = 1, Entity=e3 };
e3.Attributes.Add(a3);
e3.Id = 3;
e3.Name = "E3";
SomeEntities.Add(e3);
List<MyAttribute> attributes = new List<MyAttribute>();
attributes.Add(a1);
attributes.Add(a2);
attributes.Add(a3);
int typeId = 1;
bool orderAscending = true;
string columnName = "StringColumn";
var query = (from ent in SomeEntities
where ent.Attributes.Any(a => a.AttributeTypeId == typeId)
select ent.Attributes).SelectMany(a => a).AsQueryable();
query.Dump("Pre Ordering");
if(orderAscending)
{
// query = is needed
query = query.OrderBy(att => MyEntity.GetPropertyValue(att, columnName));
}
else
{
query = query.OrderByDescending(att => MyEntity.GetPropertyValue(att, columnName));
}
// returns a list of MyAttributes. If you need to get a list of attributes, add a MyEntity property to the MyAttribute class and populate it
var results = query.Select(att => att.Entity).ToList().Dump();
}
// Define other methods and classes here
}
class MyAttribute
{
public int Id { get; set; }
public decimal DecimalColumn { get; set; }
public string StringColumn { get; set; }
public int EntityId { get; set; }
public int AttributeTypeId { get; set; }
// having this property will require an Include in EF to return it then query, which is less effecient than the original ObjectQuery< for the question
public MyEntity Entity { get; set; }
}
class MyEntity
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<MyAttribute> Attributes { get; set; }
public MyEntity()
{
this.Attributes = new List<MyAttribute>();
}
// this could have been on any class, I stuck it here for ease of use in LINQPad
// caution reflection may be slow
public static object GetPropertyValue(object obj, string property)
{
// from Kjetil Watnedal on http://stackoverflow.com/questions/41244/dynamic-linq-orderby
System.Reflection.PropertyInfo propertyInfo=obj.GetType().GetProperty(property);
return propertyInfo.GetValue(obj, null);
}