I'm using EF Core 2.2 to connect to an Oracle11g database (using these old versions is a must), when I try to query one of my tables it throws a NullReferenceException in the EF Core external code.
The Query:
string username = "Administrator";
var user = _context.Users.Where(x => x.UserName.ToUpper() == username.ToUpper())
.Include(x => x.UserProfile)
.Include(x => x.UserRoleOrganizations).ThenInclude(s => s.Role)
.Include(x => x.UserRoleOrganizations).ThenInclude(s => s.Organization);
return user.FirstOrDefault();
The exception:
System.NullReferenceException
HResult=0x80004003
Message=Object reference not set to an instance of an object.
Source=Microsoft.EntityFrameworkCore.Relational
StackTrace:
at Microsoft.EntityFrameworkCore.Storage.TypedRelationalValueBufferFactoryFactory.CreateGetValueExpression(Expression dataReaderExpression, Int32 index, TypeMaterializationInfo materializationInfo, Boolean detailedErrorsEnabled, Boolean box)
at Microsoft.EntityFrameworkCore.Storage.TypedRelationalValueBufferFactoryFactory.<>c__DisplayClass13_0.<CreateArrayInitializer>b__0(TypeMaterializationInfo mi, Int32 i)
at System.Linq.Enumerable.<SelectIterator>d__5`2.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Dynamic.Utils.CollectionExtensions.ToReadOnly[T](IEnumerable`1 enumerable)
at System.Linq.Expressions.Expression.NewArrayInit(Type type, IEnumerable`1 initializers)
at Microsoft.EntityFrameworkCore.Storage.TypedRelationalValueBufferFactoryFactory.CreateArrayInitializer(CacheKey cacheKey, Boolean detailedErrorsEnabled)
at Microsoft.EntityFrameworkCore.Storage.TypedRelationalValueBufferFactoryFactory.<Create>b__11_0(CacheKey k)
at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
at Microsoft.EntityFrameworkCore.Internal.NonCapturingLazyInitializer.EnsureInitialized[TParam,TValue](TValue& target, TParam param, Func`2 valueFactory)
at Microsoft.EntityFrameworkCore.Query.Internal.ShaperCommandContext.NotifyReaderCreated(DbDataReader dataReader)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.Enumerator.BufferlessMoveNext(DbContext _, Boolean buffer)
at Oracle.EntityFrameworkCore.Storage.Internal.OracleExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.Enumerator.MoveNext()
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source)
at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ResultEnumerable`1.GetEnumerator()
at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.<_TrackEntities>d__17`2.MoveNext()
at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext()
at System.Linq.Enumerable.First[TSource](IEnumerable`1 source)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass15_1`1.<CompileQueryCore>b__0(QueryContext qc)
at Base.Security.Business.UserManagement.GetUser(String username) in C:\Users\Afagh2\source\repos\HSE2_Oracle\Base.Security\Business\UserManagement.cs:line 88
The generated query:
[Parameters=[:ToUpper_0='ADMINISTRATOR' (Size = 256)], CommandType='Text', CommandTimeout='0']
Select
K0 "Id", K1 "AccessFailedCount", K2 "ConcurrencyStamp", K3 "Email", K4 "EmailConfirmed", K5 "FirstName", K6 "IsActive", K7 "IsExternal", K8 "LastName", K9 "LockoutEnabled", K10 "LockoutEnd", K11 "NationalCode", K12 "NormalizedEmail", K13 "NormalizedUserName", K14 "OrganizationName", K15 "OrganizationType", K16 "PasswordHash", K17 "PersonnelCode", K18 "PhoneNumber", K19 "PhoneNumberConfirmed", K20 "PositionName", K21 "SecurityStamp", K22 "TwoFactorEnabled", K23 "UserName", K24 "Id", K25 "FileContent", K26 "FileExtension", K27 "FileName" from(
SELECT "x"."Id" K0, "x"."AccessFailedCount" K1, "x"."ConcurrencyStamp" K2, "x"."Email" K3, "x"."EmailConfirmed" K4, "x"."FirstName" K5, "x"."IsActive" K6, "x"."IsExternal" K7, "x"."LastName" K8, "x"."LockoutEnabled" K9, "x"."LockoutEnd" K10, "x"."NationalCode" K11, "x"."NormalizedEmail" K12, "x"."NormalizedUserName" K13, "x"."OrganizationName" K14, "x"."OrganizationType" K15, "x"."PasswordHash" K16, "x"."PersonnelCode" K17, "x"."PhoneNumber" K18, "x"."PhoneNumberConfirmed" K19, "x"."PositionName" K20, "x"."SecurityStamp" K21, "x"."TwoFactorEnabled" K22, "x"."UserName" K23, "x.UserProfile"."Id" K24, "x.UserProfile"."FileContent" K25, "x.UserProfile"."FileExtension" K26, "x.UserProfile"."FileName" K27
FROM "AppUser" "x"
LEFT JOIN "AppUserProfilePicture" "x.UserProfile" ON ("x"."Id" = "x.UserProfile"."Id")
WHERE ("UPPER"("x"."UserName") = :ToUpper_0)
ORDER BY "x"."Id"
) "m1"
where rownum <= 1
The query works as expected in Oracle SQL Developer. It seems that EF Core has some trouble converting the results to the entity (which in this case, the result should be none because the table has no data.) but I can not debug its code. (Or can I?)
The entity is configured in OnModelCreating:
builder.Entity<ApplicationUser>(b =>
{
b.HasKey(u => u.Id);
b.HasIndex(u => u.NormalizedUserName).HasName("UserNameIndex").IsUnique();
b.HasIndex(u => u.NormalizedEmail).HasName("EmailIndex");
b.ToTable("AppUser");
b.Property(u => u.ConcurrencyStamp).IsConcurrencyToken();
b.Property(u => u.UserName).HasMaxLength(256);
b.Property(u => u.NormalizedUserName).HasMaxLength(256);
b.Property(u => u.Email).HasMaxLength(256);
b.Property(u => u.NormalizedEmail).HasMaxLength(256);
b.HasOne(x => x.UserProfile).WithOne(x => x.User).HasForeignKey<ApplicationUserProfilePicture>(x => x.Id);
b.HasMany<IdentityUserClaim<int>>().WithOne().HasForeignKey(uc => uc.UserId).IsRequired();
b.HasMany<IdentityUserLogin<int>>().WithOne().HasForeignKey(ul => ul.UserId).IsRequired();
b.HasMany<IdentityUserToken<int>>().WithOne().HasForeignKey(ut => ut.UserId).IsRequired();
b.HasMany<ApplicationUserRoleOrganization>().WithOne().HasForeignKey(ur => ur.UserId).IsRequired();
});
I can create and query other tables. Only AppUser table doesn't work in Oracle, it works fine using the SQL Server provider.
Tell me if more info about anything is needed.
Update
User class:
public class ApplicationUser : Microsoft.AspNetCore.Identity.IdentityUser<int>
{
public string FirstName { get; set; }
public string LastName { get; set; }
[StringLength(450)]
public string PositionName { get; set; }
[StringLength(450)]
public string NationalCode { get; set; }
[StringLength(450)]
public string PersonnelCode { get; set; }
public bool IsActive { get; set; } = true;
public bool IsExternal { get; set; } = false;
[Required]
public OrganizationTypeEnum OrganizationType { get; set; }
public string OrganizationName { get; set; }
public virtual ApplicationUserProfilePicture UserProfile { get; set; }
public virtual ICollection<ApplicationUserRoleOrganization> UserRoleOrganizations { get; set; }
}
UserRoleOrganization class:
public class ApplicationUserRoleOrganization : Microsoft.AspNetCore.Identity.IdentityUserRole<int>
{
public int Id { get; set; }
public override int RoleId { get => base.RoleId; set => base.RoleId = value; }
public override int UserId { get => base.UserId; set => base.UserId = value; }
public virtual int OrganizationId { get; set; }
public virtual ApplicationUser User { get; set; }
public virtual ApplicationRole Role { get; set; }
public virtual Organization Organization { get; set; }
}
The configuration of UserRoleOrganization:
builder.Entity<ApplicationUserRoleOrganization>(b =>
{
b.HasKey(r => r.Id);
b.HasIndex(t => t.OrganizationId).HasName("OrganizationIndex");
b.HasIndex(t => t.RoleId).HasName("RoleIndex");
b.HasIndex(t => t.UserId).HasName("UserIndex");
b.HasOne(userRole => userRole.Role)
.WithMany(role => role.UserRoleOrganizations)
.HasForeignKey(userRole => userRole.RoleId).HasConstraintName("FK_RoleOrg_Role_RoleId");
b.HasOne(userRole => userRole.Organization)
.WithMany(role => role.UserRoleOrganizations)
.HasForeignKey(userRole => userRole.OrganizationId).HasConstraintName("FK_RoleOrg_Org_OrgId");
b.HasOne(userRole => userRole.User)
.WithMany(user => user.UserRoleOrganizations)
.HasForeignKey(userRole => userRole.UserId).HasConstraintName("FK_RoleOrg_User_UserId");
b.ToTable("AppUserRoleOrganization");
});
I just noticed a text is written in my output just before the exception:
fail: Microsoft.EntityFrameworkCore.Query[10100]
An exception occurred while iterating over the results of a query for context type 'Base.Security.Data.SecurityDbContext'.
You have included UserRoleOrganizations twice . Try to do including the good old way:
string username = "Administrator";
return _context.Users.Where(x => x.UserName.ToUpper() == username.ToUpper())
.Include(x => x.UserProfile)
.Include("UserRoleOrganizations.Organization");
.Include("UserRoleOrganizations.Role")
.FirstOrDefault();
But I don't like UserRoleOrganizations.Organization . Could you please show your User and UserRoleOrganizations clases?
Update.
Since I can see the classes, I think you can try to reverse query, maybe just for testing:
var userRoleOrganizations=_context.UserRoleOrganizations
.Where(x => x.User.UserName.ToUpper() == username.ToUpper())
.Include(s => s.Role)
.Include(s => s.Organization)
.ToList();
var user = _context.Users
.Where(x => x.UserName.ToUpper() == username.ToUpper())
.Include(x => x.UserProfile)
.FirstOrDefault();
user.UserRoleOrganizations=userRoleOrganizations;
return user;
And try to change the UserRoleOrganizations class:
public class UserRoleOrganizations
{
....
public override int? RoleId { get => base.RoleId; set => base.RoleId = value; }
public override int? UserId { get => base.UserId; set => base.UserId = value; }
public virtual int? OrganizationId { get; set; }
....
}
Related
how do I write this query in LINQ (c# EF6)?
Can someone please help me here - I am new to Entity Framework Structure - So bit hard for me to use different clauses
SELECT
sum(remainingamount) TotalSiteCreditAmount,
max(expirationutcdatetime) MaxExpiryDate
FROM
WholesaleCredits
WHERE
ExpirationUTCDateTime > Getdate()
GROUP BY
registeredcustomerid,
siteid
HAVING
registeredcustomerid = :registeredCustomerId
AND siteid = :siteId
Tried below thing as of now :
var data = context.WholesaleCredit
.Where(x => x.ExpirationUTCDateTime > DateTime.Now)
.GroupBy (x => x.RegisteredCustomerId)
Entity Used in code:
public partial class WholesaleCredits
{
public virtual int Id { get; set; }
public virtual decimal CreditAmount { get; set; }
public virtual decimal RemainingAmount { get; set; }
public virtual Site Site { get; set; }
public virtual DateTime GeneratedUTCDateTime { get; set; }
public virtual DateTime ExpirationUTCDateTime { get; set; }
public virtual int RegisteredCustomerId { get; set; }
}
You do not need HAVING here and Grouping should be provided by constant, because you have filter on grouping keys:
var data = context.WholesaleCredit
.Where(x => x.ExpirationUTCDateTime > DateTime.Now && x.RegisteredCustomerId == registeredCustomerId && x.Site.Id == siteid)
.GroupBy(x => 1)
.Select(g => new
{
TotalSiteCreditAmount = g.Sum(x => x.RemainingAmount),
MaxExpiryDate = g.Max(x => x.ExpirationUTCDateTime)
})
.First();
I want to display related data from second table with each value in first table
i have tried this query
public ActionResult Index()
{
List<EmployeeAtt> empWithDate = new List<EmployeeAtt>();
var employeelist = _context.TblEmployee.ToList();
foreach (var employee in employeelist)
{
var employeeAtt = _context.AttendanceTable
.GroupBy(a => a.DateAndTime.Date)
.Select(g => new EmployeeAtt
{
Date = g.Key,
Emp_name = employee.EmployeeName,
InTime = g.Any(e => e.ScanType == "I") ? g.Where(e =>
e.ScanType == "I").Min(e =>
e.DateAndTime.ToShortTimeString())
.ToString() : "Absent",
OutTime = g.Any(e => e.ScanType == "O") ? g.Where(e =>
e.ScanType == "O").Max(e =>
e.DateAndTime.ToShortTimeString())
.ToString() : "Absent"
});
empWithDate.AddRange(employeeAtt);
}
return View(empWithDate);
}
Here is my attendance Table
AttendanceTable
Results
I want to display the shortest time with "I" Column value against each employee and last time with "O" Column value as out time. I think i am not using AddRange() at proper place. Where it should go then?
public partial class TblEmployee
{
public TblEmployee()
{
AttendanceTable = new HashSet<AttendanceTable>();
}
public int EmpId { get; set; }
public string EmployeeName { get; set; }
public virtual ICollection<AttendanceTable> AttendanceTable { get; set; }
}
public partial class AttendanceTable
{
public int Id { get; set; }
public int AttendanceId { get; set; }
public int EmployeeId { get; set; }
public string ScanType { get; set; }
public DateTime DateAndTime { get; set; }
public virtual TblEmployee Employee { get; set; }
}
The actual problem is not related to AddRange(), you need a where clause before GroupBy() to limit attendances (before grouping) to only records related to that specific employee, e.g.
_context.AttendanceTable
.Where(a => a.Employee == employee.EmployeeName)
.GroupBy(a => a.DateAndTime.Date)
...
Depended on your model, it is better to use some kind of ID instead of EmployeeName for comparison if possible.
Also you can use SelectMany() instead of for loop and AddRange() to combine the results into a single list. like this:
List<EmployeeAtt> empWithDate = _context.TblEmployee.ToList()
.SelectMany(employee =>
_context.AttendanceTable
.Where(a => a.Employee == employee.EmployeeName)
.GroupBy(a => a.DateAndTime.Date)
.Select(g => new EmployeeAtt
{
...
})
);
...
I have defined the following state machine in Masstransit:
public class OrderStateMachine : MassTransitStateMachine<OrderState>
{
public OrderStateMachine()
{
InstanceState(x => x.Status);
Event(() => OrderCreated, x => x.CorrelateBy(order => order.OrderCode, ctx => ctx.Message.OrderCode).SelectId(ctx => NewId.NextGuid()));
//How should I select an id for these events?
Event(() => OrderProvisioned, x => x.CorrelateBy(order => order.OrderCode, ctx => ctx.Message.OrderCode));
Event(() => OrderInvoiced, x => x.CorrelateBy(order => order.OrderCode, ctx => ctx.Message.OrderCode));
State(() => Created);
State(() => Finished);
CompositeEvent(() => OrderFinished, order => order.CompositeStatus, OrderProvisioned, OrderInvoiced);
Initially(
When(OrderCreated)
.Then(context => Console.WriteLine("Order created"))
.TransitionTo(Created));
During(Created,
When(OrderFinished)
.Then(context => Console.WriteLine("Order finished"))
.TransitionTo(Finished)
.Finalize());
}
public State Created { get; set; }
public State Finished { get; set; }
public Event<OrderCreated> OrderCreated { get; set; }
public Event<OrderProvisioned> OrderProvisioned { get; set; }
public Event<OrderInvoiced> OrderInvoiced { get; set; }
public Event OrderFinished { get; set; }
}
public class OrderState : SagaStateMachineInstance
{
public Guid CorrelationId { get; set; }
public string OrderCode { get; set; }
public string Status { get; set; }
public CompositeEventStatus CompositeStatus { get; set; }
}
public class OrderCreated
{
public string OrderCode { get; set; }
public OrderCreated(string orderCode)
{
OrderCode = orderCode;
}
}
public class OrderInvoiced
{
public string OrderCode { get; set; }
public OrderInvoiced(string orderCode)
{
OrderCode = orderCode;
}
}
public class OrderProvisioned
{
public string OrderCode { get; set; }
public OrderProvisioned(string orderCode)
{
OrderCode = orderCode;
}
}
How can I correlate the OrderProvisoned and the OrderInvoiced event to the same OrderState instance as the initial OrderCreated event without sending Guids in my events and only use the ordercode property to correlate them?
If I run this example, I never get the OrderFinished event if both OrderProvisioned and OrderInvoiced are sent, but if I add Guids to the events and correlate them based on that Guid it is executed correctly.
I solved it, apparently you must explicitly set the custom correlationid on the statemachine instance, I would expect that masstransit would do that for me.
So this is the adapted code for the initial state:
Initially(
When(OrderCreated)
.Then(context => context.Instance.OrderCode = context.Data.OrderCode)
.Then(context => Console.WriteLine("Order created"))
.TransitionTo(Created));
Also using a saga factory works, but I would think that you could drop the selectid logic as the saga factory overrules this, but than I get an exception.
Event(() => OrderCreated,
x =>
{
x.CorrelateBy(order => order.OrderCode, ctx => ctx.Message.OrderCode);
x.InsertOnInitial = true;
x.SetSagaFactory(context => new OrderState
{
CorrelationId = NewId.NextGuid(),
OrderCode = context.Message.OrderCode
});
x.SelectId(context => NewId.NextGuid());
});
Is there a broken unit test that would be usable to see if this behavior is not working? From the edited code above, that seems to be how I would expect it to be correlated - using the OrderCode. That's how the shopping cart example works as well.
The SelectId() function is only needed for an event that initiates the state machine, since that is where the CorrelationId is assigned. If it isn't used by the other events, there is no need to "select" it since the Query (the orderCode) would do the correlation.
That's how the shopping cart example works in the sample:
https://github.com/MassTransit/Sample-ShoppingWeb/blob/master/src/CartTracking/ShoppingCartStateMachine.cs#L15
In your example above:
Event(() => OrderCreated, x => x.CorrelateBy(order => order.OrderCode, ctx => ctx.Message.OrderCode).SelectId(ctx => NewId.NextGuid()));
Event(() => OrderProvisioned, x => x.CorrelateBy(order => order.OrderCode, ctx => ctx.Message.OrderCode));
Event(() => OrderInvoiced, x => x.CorrelateBy(order => order.OrderCode, ctx => ctx.Message.OrderCode));
This is exactly how I would configure it, and it seems like you're on the right track.
I have the following entities:
public class Mark {
public Int32 Id { get; set; }
public DateTime Created { get; set; }
public virtual ICollection<MarkLocalized> MarksLocalized { get; set; }
} // Mark
public class MarkLocalized {
public Int32 Id { get; set; }
public String Culture { get; set; }
public String Name { get; set; }
public virtual Mark Mark { get; set; }
} // MarkLocalized
Given a culture, for example "en", I need to get all Marks, their localized name for the given culture and map them to a MarkModel:
public class MarkModel {
public Int32 Id { get; set; }
public String Name { get; set; }
} // MarkModel
I tried the following:
Context context = new Context();
context.Marks
.SelectMany(x => x.MarksLocalized, (y, z) =>
new MarkModel {
Id = y.Id,
Name = z.Name,
Slug = z.Slug
});
But I need only the MarksLocalized which culture is equal to the given culture.
How can I do this?
Thank you,
Miguel
You can add Where inside SelectMany:
Context context = new Context();
context.Marks
.SelectMany(x => x.MarksLocalized.Where(x => x.Culture == "en"), (y, z) =>
new MarkModel {
Id = y.Id,
Name = z.Name,
Slug = z.Slug
});
But should be more clear with syntax query:
from m in new Context.Marks
from l in m.MarksLocalized
where l.Culture == "en"
select new MarkModel { m.ID, m.Name, l.Slug }
which should be translated into following query by compiler:
context.Marks
.SelectMany(x => x.MarksLocalized, (m, l) => new { m, l })
.Where(x => x.l.Culture == "en")
.Select(x => new MarkModel { x.m.ID, x.m.Name, x.l.Slug })
which should produce exact same results as first one.
I've got the following query:
model.Page = db.Pages
.Where(p => p.PageId == Id)
.Include(p => p.Series
.Select(c => c.Comics
.Select(col => col.Collection)))
.SingleOrDefault();
This works great, although I now need to order the Comics by a property called 'ReadingOrder'.
I've tried:
model.Page = db.Pages
.Where(p => p.PageId == Id)
.Include(p => p.Series.Select(c => c.Comics.OrderBy(o => o.ReadingOrder)
.Select(col => col.Collection)))
.SingleOrDefault();
But this results in the following error:
The Include path expression must refer to a navigation property
defined on the type. Use dotted paths for reference navigation
properties and the Select operator for collection navigation
properties. Parameter name: path
Any ideas what this error means?
Thanks in advance
EDIT:
My models:
public class Page
{
public int PageId { get; set; }
public string Title { get; set; }
public ICollection<Series> Series { get; set; }
}
public class Series
{
public int SeriesId { get; set; }
public int PageId { get; set; }
public string Title { get; set; }
public Page Page { get; set; }
public ICollection<Comic> Comics { get; set; }
}
public class Comic
{
public int ComicId { get; set; }
public string Title { get; set; }
public int ReadingOrder { get; set; }
public string Subtitle { get; set; }
public int CollectionId { get; set; }
public Collection Collection { get; set; }
}
public class Collection
{
public int CollectionId { get; set; }
public string Title { get; set; }
public ICollection<Comic> Comics { get; set; }
}
The exception "...Include path expression must refer to a navigation property..." basically complains that c.Comics.OrderBy is not a navigation property. (It's a legitimate complaint, I think.)
Actually it's not supported by EF to apply sorting (and also filtering) in eager loading statements (Include).
So, what can you do?
Option 1:
Sort in memory after you have loaded the entity:
model.Page = db.Pages
.Where(p => p.PageId == Id)
.Include(p => p.Series.Select(c => c.Comics
.Select(col => col.Collection)))
.SingleOrDefault();
if (model.Page != null)
{
foreach (var series in model.Page.Series)
series.Comics = series.Comics.OrderBy(c => c.ReadingOrder).ToList();
}
Ugly, but because you are loading apparently only a single Page object by id it's possibly faster (LINQ to Objects in memory) than the following options (if Series and Comics collections are not extraordinarily long).
Option 2:
Break down the query in parts which mix eager and explicite loading:
model.Page = db.Pages
.Where(p => p.PageId == Id)
.Include(p => p.Series) // only Series collection is included
.SingleOrDefault();
if (model.Page != null)
{
foreach (var series in model.Page.Series)
db.Entry(series).Collection(s => s.Comics).Query()
.Include(c => c.Collection)
.OrderBy(c => c.ReadingOrder)
.Load(); // one new DB query for each series in loop
}
Option 3:
Projection?
Here and here is by the way something about the dangers of complex Include chains of multiple navigation properties. It can load huge amounts of duplicated data. Include ensures that you only have one DB roundtrip but possibly at the cost of much more transfered data. Explicite loading has multiple roundtrips but with possibly less data in total.
(I know, I gave you this Include...Select...Select...Select... chain, but how could I know that you would take me serious :). Well, depending on the size of your nested collections it can still be the best option.)
Off the top of my head, untested:
model.Page = db.Pages
.Where(p => p.PageId == Id)
.Include(p => p.Series
.Select(c => c.Comics
.Select(col => col.Collection)
.OrderBy(o => o.ReadingOrder)))
.SingleOrDefault();