Why is reading a Dapper IEnumerable(dynamic) an order of magnitude slower than reading an IEnumerable(IDataRecord)? - enterprise-library

In comparing Dapper with the Enterprise Library Data Access Access block for getting data via stored procedure. I see an overall performance benefit of about 40% when using Dapper, which is somewhat surprising.
However, when comparing iteration and getting data from an IEnumerable(IDataRecord) vs. IEnumerable(dynamic), IEnumerable(IDataRecord) is approximately an order of magnitude faster. Is this behavior well understood and to be expected or is there something not right here?
The results:
IEnumerable(IDataRecord)
IEnumerable(dynamic) - using dapperObject.propertyName
Now the interesting part, when using dapperObject["propertyName"], the performance is on par with IDataRecord. Not at all what I would have expected.
The relevant portion of the profiling code
using System;
using System.Collections.Generic;
using System.Linq;
using Dapper.DataAccess;
using System.Data;
using tophat;
namespace Dapper.TestRunner
{
class Program
{
static void Main(string[] args)
{
var connectionString = "data source=WEBDBdev3,1866; User id=hsbmhw;Password=gEner4Y&M;Persist Security Info='true'; initial catalog=myhomeworks;";
//The following uses Tophat to create a singleton connection instance.
Database.Install<SqlServerConnectionFactory>(connectionString, ConnectionScope.ByRequest);
DapperTest();
DapperTest2();
EnterpriseLibraryIDataRecordTest();
}
private static void DapperTest()
{
for (int i = 0; i < 100; i++)
{
IEnumerable<dynamic> users = MyRepository.GetUsersDapper();
PopulateBusinessObjectsDynamic(users);
}
}
private static void DapperTest2()
{
for (int i = 0; i < 100; i++)
{
IEnumerable<dynamic> users = MyRepository.GetUsersDapper();
PopulateBusinessObjectsDynamic2(users);
}
}
private static void EnterpriseLibraryIDataRecordTest()
{
for (int i = 0; i < 100; i++)
{
IEnumerable<IDataRecord> users = MyRepository.GetUsersEntlib();
PopulateBusinessObjectsIDataRecord(users);
}
}
private static void PopulateBusinessObjectsDynamic(IEnumerable<dynamic> users)
{
foreach (var user in users)
{
BusinessObject bo = new BusinessObject(user);
}
}
private static void PopulateBusinessObjectsDynamic2(IEnumerable<dynamic> users)
{
foreach (var user in users)
{
BusinessObject bo = new BusinessObject(user);
}
}
private static void PopulateBusinessObjectsIDataRecord(IEnumerable<IDataRecord> users)
{
foreach (var user in users)
{
BusinessObject bo = new BusinessObject(user);
}
}
}
public class BusinessObject
{
public DateTime CreateDate { get; set; }
public String CreateDateString { get; set; }
public String FirstName { get; set; }
public bool IsApproved { get; set; }
public bool IsLockedOut { get; set; }
public DateTime LastActivityDate { get; set; }
public DateTime LastLoginDate { get; set; }
public String LastName {get;set;}
public String Organization{get;set;}
public int OrganizationId{get;set;}
public int PersonId{get;set;}
public String ProfileLastUpdatedBy{get;set;}
public DateTime ProfileLastUpdatedDate{get;set;}
public String RoleName{get;set;}
public long RowNumber{get;set;}
public int TotalCount{get;set;}
public Guid UserId{get;set;}
public string UserName {get;set;}
public string UserStatus{get;set;}
public BusinessObject(dynamic user)
{
CreateDate=user.CreateDate;
CreateDateString = user.CreateDateString;
FirstName = user.FirstName;
IsApproved = user.IsApproved;
IsLockedOut = user.IsLockedOut;
LastActivityDate= user.LastActivityDate;
LastLoginDate = user.LastLoginDate;
LastName = user.LastName;
Organization = user.organization;
OrganizationId=user.organization_id;
PersonId = user.party_id;
ProfileLastUpdatedBy = user.ProfileLastUpdatedBy;
ProfileLastUpdatedDate = user.ProfileLastUpdatedDate;
RoleName = user.RoleName;
RowNumber = user.RowNumber;
TotalCount = user.TotalCount;
UserId = user.UserId;
UserName= user.UserName;
UserStatus = user.UserStatus;
}
public BusinessObject(bool x, dynamic user)
{
CreateDate = user["CreateDate"];
CreateDateString = user["CreateDateString"];
FirstName = user["FirstName"];
IsApproved = user["IsApproved"];
IsLockedOut = user["IsLockedOut"];
LastActivityDate = user["LastActivityDate"];
LastLoginDate = user["LastLoginDate"];
LastName = user["LastName"];
Organization = user["organization"];
OrganizationId = user["organization_id"];
PersonId = user["party_id"];
ProfileLastUpdatedBy = user["ProfileLastUpdatedBy"];
ProfileLastUpdatedDate = user["ProfileLastUpdatedDate"];
RoleName = user["RoleName"];
RowNumber = user["RowNumber"];
TotalCount = user["TotalCount"];
UserId = user["UserId"];
UserName = user["UserName"];
UserStatus = user["UserStatus"];
}
public BusinessObject(IDataRecord user)
{
CreateDate = (DateTime)user["CreateDate"];
CreateDateString = (string)user["CreateDateString"];
FirstName = (string)user["FirstName"];
IsApproved = (bool)user["IsApproved"];
IsLockedOut = (bool)user["IsLockedOut"];
LastActivityDate = (DateTime)user["LastActivityDate"];
LastLoginDate = (DateTime)user["LastLoginDate"];
LastName = (string)user["LastName"];
Organization = (string)user["organization"];
OrganizationId = (int)user["organization_id"];
PersonId = (int)user["party_id"];
ProfileLastUpdatedBy = (string)user["ProfileLastUpdatedBy"];
ProfileLastUpdatedDate = (DateTime)user["ProfileLastUpdatedDate"];
RoleName = (string)user["RoleName"];
RowNumber = (long)user["RowNumber"];
TotalCount = (int)user["TotalCount"];
UserId = (Guid)user["UserId"];
UserName = (string)user["UserName"];
UserStatus = (string)user["UserStatus"];
}
}
}

You actually seem to be running the same test twice; the input (users) is the same, from here:
private static void DapperTest()
{
for (int i = 0; i < 100; i++)
{
IEnumerable<dynamic> users = MyRepository.GetUsersDapper();
PopulateBusinessObjectsDynamic(users);
}
}
private static void DapperTest2()
{
for (int i = 0; i < 100; i++)
{
IEnumerable<dynamic> users = MyRepository.GetUsersDapper();
PopulateBusinessObjectsDynamic2(users);
}
}
And the actual "what do we do" is the same, here:
private static void PopulateBusinessObjectsDynamic(IEnumerable<dynamic> users)
{
foreach (var user in users)
{
BusinessObject bo = new BusinessObject(user);
}
}
private static void PopulateBusinessObjectsDynamic2(IEnumerable<dynamic> users)
{
foreach (var user in users)
{
BusinessObject bo = new BusinessObject(user);
}
}
So... I have to conclude "a combination of JIT, data caching (at the database server) assembly loading/validation/fusion, connection pooling, and the dynamic cache features made the second test appear faster".
Note that the dynamic side of dapper is intended for ad-hoc usage only anyway. If you wanted the optimal face of dapper, you would use Query<T>, not dynamic.
In particular, AFAIK no build of dapper supports a string indexer on the dynamic API. The object implements IDictionary<string,object> for member access, but you would need to explicitly cast to that to use it - you can't do user["PropName"] if user is typed as dynamic (if I'm wrong, please tell me!).
As it happens, the unreleased "git" code (for the dynamic API) is noticeably faster than the current "nuget" implementation - but that is a bit of a tangent to this specific question.

Related

How can get a list data of Google Sheets based on column names in Entity Framework

I'm modeling data search in Google Sheets using API (EF). I am currently connected to Google Sheets data. I also wrote a search based on RowId it's ok. Everything works fine. However I can't find data based on Id. Everything I have:
ItemGoogleSheet.cs
public class ItemGoogleSheet
{
public string Id { get; set; }
public string Name { get; set; }
}
ItemsGoogleSheetMapper.cs
public class ItemsGoogleSheetMapper
{
public static List<ItemGoogleSheet> MapFromRangeData(IList<IList<object>> values)
{
var items = new List<ItemGoogleSheet>();
foreach (var value in values)
{
ItemGoogleSheet item = new()
{
Id = value[0].ToString(),
Name = value[1].ToString(),
};
items.Add(item);
}
return items;
}
public static IList<IList<object>> MapToRangeData(ItemGoogleSheet item)
{
var objectList = new List<object>() { item.Id, item.Name };
var rangeData = new List<IList<object>> { objectList };
return rangeData;
}
}
ItemsGoogleSheetVATController.cs
public class ItemsGoogleSheetVATController : ControllerBase
{
const string SPREADSHEET_ID = "xxxx";
const string SHEET_NAME = "xx";
SpreadsheetsResource.ValuesResource _googleSheetValues;
public ItemsGoogleSheetVATController(GoogleSheetsHelper googleSheetsHelper)
{
_googleSheetValues = googleSheetsHelper.Service.Spreadsheets.Values;
}
[HttpGet("{rowId}")]
public IActionResult GetRowID(int rowId)
{
var range = $"{SHEET_NAME}!A{rowId}:AG{rowId}";
var request = _googleSheetValues.Get(SPREADSHEET_ID, range);
var response = request.Execute();
var values = response.Values;
return Ok(ItemsGoogleSheetMapper.MapFromRangeData(values).FirstOrDefault());
}
[HttpGet]
public IActionResult GetID(string id)
{
//How to get Data from Id
//return Ok();
}
}
My Google Sheets Data:
As in my description. I want to find Id = 0102 then it will output a list of results of: 0102, 01022101, 01022102
How can I get list of data based on Id column. Asking for any solutions from everyone. Thank you!
I have solved the problem. Thank you!

Entity Framework saving in the write DBSet from abstract elements

net web app in witch database first and in my models they made the join tables as models. The thing that i want to do is that when i save ore edit a normal model i want to be able to update the relationships ass well. The problem is that i have a lot of repetitive code.For instance:
for (int i = 0; i < formationVM.Traits_.Length; i++)
{
_context.FormationTraits.Add(new FormationTrait(formationVM.Formation_, await _context.Traits.FirstOrDefaultAsync(q => q.Id == formationVM.Traits_[i])));
}
for (int i = 0; i < formationVM.Factions_.Length; i++)
{
_context.FactionFormations.Add(new FactionFormation(await _context.Factions.FirstOrDefaultAsync(q => q.Id == formationVM.Factions_[i]), formationVM.Formation_));
}
for (int i = 0; i < formationVM.Items_.Length; i++)
{
_context.ItemFormations.Add(new ItemFormation(await _context.Items.FirstOrDefaultAsync(q => q.Id == formationVM.Items_[i]), formationVM.Formation_));
}
So i want to make a single function that dose all the 3 repetitive task.
I thought of using abstraction in order to do this.
public interface IJointModel
{
public int IdLeft { get; set; }
public int IdRight { get; set; }
public IModel_ GetIdNavigationLeftModel();
public void SetIdNavigationLeftModel(IModel_ modelLeft);
public IModel_ GetIdNavigationRightModel();
public void SetIdNavigationRightModel(IModel_ modelRight);
public void saveYourself(TotalWarWanaBeContext context);
}
public interface IModel_
{
public int Id { get; set; }
}
public partial class Formation : IModel_
public partial class Trait :IModel_
public partial class FormationTrait : IJointModel{
public FormationTrait(Formation formation, Trait trait)
{
this.IdFormationNavigation = formation;
this.IdTraitNavigation = trait;
this.IdRight = trait.Id;
this.IdLeft = formation.Id;
}
[ForeignKey("IdFormationNavigation")]
[Column("IdFormation")]
public int IdLeft { get; set; }
[ForeignKey("IdTraitNavigation")]
[Column("IdTrait")]
public int IdRight { get; set; }
public virtual Formation IdFormationNavigation { get; set; }
public virtual Trait IdTraitNavigation { get; set; }
#region "Get_set_IJoinModel"
public IModel_ GetIdNavigationLeftModel()
{
return this.IdFormationNavigation;
}
public IModel_ GetIdNavigationRightModel()
{
return this.IdTraitNavigation;
}
public void SetIdNavigationLeftModel(IModel_ modelLeft)
{
this.IdFormationNavigation = (Formation)modelLeft;
}
public void SetIdNavigationRightModel(IModel_ modelRight)
{
this.IdTraitNavigation = (Trait)modelRight;
}
#endregion
public void saveYourself(TotalWarWanaBeContext context)
{
context.FormationTraits.Add(this);
}
}
The end goal is so that i can make the function
private void saveRelation(string jointableType/* ore something like this*/, IModel anyModel, int[] idLeftModels){
for(int i = 0; i < idLeftModels.Length; i++){
IJointModel jointModel = new IJointModel.TypeOf(jointTableType);
jointModel.setIdNavigationLeftModel = anyModel;
jointModel.setItNavigtionRightModel = _context.DbSetofTypeINeed.where(t => t.id = idLeftModels[i];
jointModel.SaveYourself();
}
How can I make the function saveRelation save in the database any Model that inherits from IJoinModel. Sorry if the title is miss representative but i didn't know how to phrase it

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

Prevent EF from saving full object graph

I have a model as below
public class Lesson
{
public int Id { get; set; }
public Section Div { get; set; }
}
public class Section
{
public int Id { get; set; }
public string Name { get; set; }
}
I also have DB Context as below
public class MyContext : DbContext
{
public MyContext() : base("DefaultConnection")
{
this.Configuration.LazyLoadingEnabled = false;
this.Configuration.ProxyCreationEnabled = false;
}
public DbSet<Lesson> Lessons { get; set; }
public DbSet<Section> Sections { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
Then I use the following code to call the database
using (MyContext c = new EFTest.MyContext())
{
Lesson d = new EFTest.Lesson();
Section ed = new EFTest.Section() { Name = "a" };
d.Div = ed;
c.Entry(d.Div).State = EntityState.Detached;
c.Lessons.Add(d);
c.SaveChanges();
}
I am expecting this code to save just the Lesson object, not to save the full graph of Lesson and Section, but what happens is that it saves the full graph.
How do I prevent it from doing that?
When you add an entity to DbSet, entityframework will add all of its relative. You need to detach the entity you don't want to add, after adding parent entity to DbSet.
using (MyContext c = new EFTest.MyContext())
{
Lesson d = new EFTest.Lesson();
Section ed = new EFTest.Section() { Name = "a" };
d.Div = ed;
c.Lessons.Add(d);
c.Entry(d.Div).State = EntityState.Detached;
c.SaveChanges();
}
if you want to add section, related to the lesson , you need to use the same context, or create a new context and load the lesson.
you can use this code
using (MyContext c = new EFTest.MyContext())
{
Lesson d = new EFTest.Lesson();
Section ed = new EFTest.Section() { Name = "a" };
d.Div = ed;
c.Lessons.Add(d);
c.Entry(d.Div).State = EntityState.Detached;
c.SaveChanges();
//you can use this code
ed.Lesson = d;
// or this code
d.Div = ed;
c.Sections.Add(ed);
c.SaveChanges();
}

Why doesn't entity framework return any rows from related tables

I wrote this code
class Student {
public Student() {
this.Courses = new HashSet<Course>();
}
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
class Course {
public Course() {
this.Students = new HashSet<Student>();
}
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
class SchoolDBContext : DbContext {
public DbSet<Student> Students { get; set; }
public DbSet<Course> Courses { get; set; }
public SchoolDBContext()
: base("SchoolDbConnectionString") {
}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
My Seed method looks like
protected override void Seed(ConsoleApplication6.SchoolDBContext context)
{
Course c1 = new Course { ID = 1, Name = "Chemistry" };
Course c2 = new Course { ID = 2, Name = "Maths" };
Course[] courses = new Course[2];
courses[0] = c1;
courses[1] = c2;
Student s1 = new Student { ID = 1, Name = "Student 1" };
Student s2 = new Student { ID = 1, Name = "Student 2" };
Student[] students = new Student[2];
students[0] = s1;
students[1] = s2;
c1.Students = students;
c2.Students = students;
context.Courses.AddOrUpdate(course => new { course.ID }, courses);
}
After I run Update-database I can see that the database has 3 tables. Student and Course tables have 2 rows each and StudentCourse table has 4 rows. So I guess all data is seeded correctly.
Now when I write this code in my main method
static void Main(string[] args) {
SchoolDBContext c = new SchoolDBContext();
c.Configuration.LazyLoadingEnabled = true;
Student s = (from student in c.Students where student.ID == 1 select student).FirstOrDefault();
List<Course> courses = s.Courses.ToList();
Console.WriteLine(s.Name);
Console.WriteLine(courses.Count);
foreach (Course co in courses) {
Console.WriteLine(co.Name);
}
}
it prints the name of the student correctly... but prints 0 for courses.Count and the forloop on courses List returns nothing.
why am I not able to get the courses for student 1?
I also tried the other way round
static void Main(string[] args) {
SchoolDBContext c = new SchoolDBContext();
c.Configuration.LazyLoadingEnabled = true;
Course co = (from course in c.Courses where course.ID == 1 select course).FirstOrDefault();
Console.WriteLine(co.Name);
List<Student> students = co.Students.ToList();
foreach (Student s in students) {
Console.WriteLine(s.Name);
}
}
here also the name of the course is returned correctly... but it doesn't print any of the students.
So entity framework is not able to walk to the related table and fetch rows from there.
What's going on?
Found the answer myself.
static void Main(string[] args) {
SchoolDBContext c = new SchoolDBContext();
c.Configuration.LazyLoadingEnabled = false;
Course co = (from course in c.Courses.Include("Students") where course.ID == 1 select course).FirstOrDefault();
Console.WriteLine(co.Name);
List<Student> students = co.Students.ToList();
foreach (Student s in students) {
Console.WriteLine(s.Name);
}
}
putting it here so that it benefits someone....
But according to me my original code should have worked (lazy loading) so I don't understand why my original code which was doing lazy loading did not work.
OK. here is the solution with lazy loading
static void Main(string[] args) {
SchoolDBContext context = new SchoolDBContext();
context.Configuration.LazyLoadingEnabled = true;
Course co = (from course in context.Courses where course.ID == 1 select course).FirstOrDefault();
//Course co = (from course in c.Courses where course.ID == 1 select course).FirstOrDefault();
Console.WriteLine(co.Name);
foreach (Student s in context.Entry(co).Collection(c => c.Students).Query()) {
Console.WriteLine(s.Name);
}
}
This was really useful
http://msdn.microsoft.com/en-us/data/jj574232#lazy