ServiceStack OrmLite mapping with references not working - c#

I'm trying out OrmLite to see if I can replace Entity Framework in my projects. The speed is quite significant on simple queries. But I tried to map/reference a [1 to many- relation and read the documentation + examined the test code from the github page but without success. This is my example. Is there something I've forgot or should do to get it working like Entity Framework?
Example
// EF: returns +15.000 records + mapped > product.StockItems (slow)
dbContext.Products.Include(x => x.StockItems).ToList();
// OrmLite: returns +100.000 records (NO mapping > product.StockItems)
db.Select<Product>(db.From<Product>().Join<StockItem>());
// OrmLite: +15.000 separate requests to sql server (bad workarround + slow)
foreach (var product in db.Select<Product>())
{
// manual mapping
product.StockItems = db.Select<StockItem>(x => x.ProductId == product.Id);
}
Product.cs
public class Product
{
public int Id { get; set; }
public ProductType ProductType { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int DisplayOrder { get; set; }
public bool LimitedToStores { get; set; }
public string Sku { get; set; }
public decimal Price { get; set; }
public decimal OldPrice { get; set; }
public decimal SpecialPrice { get; set; }
public decimal DiscountPercentage { get; set; }
public DateTime? DateChanged { get; set; }
public DateTime? DateCreated { get; set; }
//...
[Reference]
public virtual IList<StockItem> StockItems { get; set; } = new List<StockItem>();
}
StockItem.cs
public class StockItem
{
public int Id {get; set;}
[References(typeof(Product))]
public int ProductId { get; set; }
public string Size { get; set; }
public int TotalStockQuantity { get; set; }
public string Gtin { get; set; }
public int DisplayOrder { get; set; }
// ...
[Reference]
public virtual Product Product { get; set; }
}

Ideally your POCOs/DTOs shouldn't use interfaces and you don't need to use virtual as ORM only populates your own POCOs (i.e. it doesn't create proxies of your models like other Heavy ORMs), I also prefer to use [AutoIncrement] for integer Ids (unless you need to populate specific Ids) so my Models would look like:
public class Product
{
[AutoIncrement]
public int Id { get; set; }
public ProductType ProductType { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int DisplayOrder { get; set; }
public bool LimitedToStores { get; set; }
public string Sku { get; set; }
public decimal Price { get; set; }
public decimal OldPrice { get; set; }
public decimal SpecialPrice { get; set; }
public decimal DiscountPercentage { get; set; }
public DateTime? DateChanged { get; set; }
public DateTime? DateCreated { get; set; }
[Reference]
public List<StockItem> StockItems { get; set; }
}
public class StockItem
{
[AutoIncrement]
public int Id { get; set; }
[References(typeof(Product))]
public int ProductId { get; set; }
public string Size { get; set; }
public int TotalStockQuantity { get; set; }
public string Gtin { get; set; }
public int DisplayOrder { get; set; }
}
OrmLite's POCO References only populate 1-level deep and it's not a good idea to have cyclical relationships as they're not serializable so I'd remove the back reference on StockItems as it's not going to be populated.
You also need to use LoadSelect in order to query and return POCOs with references, so to return Product with their StockItem references you can just do:
db.LoadSelect<Product>();
You can also populate this manually with 2 queries by using Merge extension method to merge 2 disconnected record sets, e.g:
var q = db.From<Product>().Join<StockItem>();
var products = db.Select(q.SelectDistinct());
var stockItems = db.Select<StockItem>();
products.Merge(stockItems);
Which will merge Products with their StockItems which you can quickly see by running:
products.PrintDump();

Related

The expected type was 'System.Nullable`1[System.Guid]' but the actual value was null

An exception occurred while reading a database value for property 'EMWH.UniqueAttchID'. The expected type was 'System.Nullable`1[System.Guid]' but the actual value was null.
I'm using EFCore 5.0 and I get the error listed above. If in my EMWH view I hide all records where there is a NULL in UniqueAttchID it works fine. But I can't seem to find a way to exclude the records where the principal key (for the relationship) is NULL. But still have the ability to view all records.
Code causing the error
var workOrder = await _context.EMWHs.AsNoTracking()
.Include(x => x.EMWIs).ThenInclude(x => x.HQATs)
.Where(x => x.KeyID == WorkOrderKeyId).SingleOrDefault();
EMWH
public class EMWH
{
public byte EMCo { get; set; }
public string WorkOrder { get; set; }
public string Equipment { get; set; }
public string? Description { get; set; }
public Guid? UniqueAttchID { get; set; }
[Column("udServiceRecordYN")]
public string? ServiceRecordYN { get; set; }
public char Complete { get; set; }
public long KeyID { get; set; }
[Column("DateSched")]
[Display(Name = "Scheduled Date")]
public DateTime ScheduledDate { get; set; }
public virtual EMEM EMEM { get; set; }
public virtual IEnumerable<EMWI> EMWIs { get; set; }
public virtual IEnumerable<HQAT> HQATs { get; set; }
}
HQAT
public class HQAT
{
public byte HQCo { get; set; }
public string FormName { get; set; }
public string KeyField { get; set; }
public string Description { get; set; }
public string AddedBy { get; set; }
public DateTime? AddDate { get; set; }
public string DocName { get; set; }
public int AttachmentID { get; set; }
public string TableName { get; set; }
public Guid? UniqueAttchID { get; set; }
public string OrigFileName { get; set; }
public string DocAttchYN { get; set; }
public string CurrentState { get; set; }
public int? AttachmentTypeID { get; set; }
public string IsEmail { get; set; }
public long KeyID { get; set; }
public virtual udEMCD EMCD { get; set; }
public virtual HQAF HQAF { get; set; }
public virtual EMWH EMWH { get; set; }
public virtual EMWI EMWI { get; set; }
public virtual udEMED EMED { get; set; }
}
DBContext
modelBuilder.Entity<EMWH>().ToTable("EMWH").HasKey(k=>new { k.EMCo, k.WorkOrder });
modelBuilder.Entity<HQAT>().HasOne(x => x.EMWH).WithMany(x => x.HQATs).HasForeignKey(x => x.UniqueAttchID)
.HasPrincipalKey(x => x.UniqueAttchID);
You have the relationship set up to count on public Guid? UniqueAttchID { get; set; } for keys between entities, but you have this set up as a nullable GUID type, so when the query runs the DB is likely coming across a null value in the table, and can't resolve the relationship. The simple solution and arguably best practice is to make those properties non-nullable, or define the relationship using int or long types as ID's, so they can't be null, and making sure your INSERT and UPDATE queries are properly setting the relationships. Either way, the null is where you should start and if you have records in the tables already that have null values you are expecting to use as keys, you could have some work on your hands to figure out how those are supposed to be linked and getting the nulls replaced with GUID values.

Save complexa data using entity framework

Hi every one I want to save complex data using Entity Framework and C#. I have 2 classes Product and Order defined as follows
Product Class
public class Product
{
[Key]
public int Id { get; set; }
public string SKU_Code { get; set; }
public string Product_Name { get; set; }
public string Quantity { get; set; }
public string Price { get; set; }
public string Image { get; set; }
public DateTime Created_Date { get; set; }
public DateTime Modified_Date { get; set; }
}
Order Class
public class Order
{
[Key]
public long ID { get; set; }
public string Order_Id { get; set; }
public string Payment_Type { get; set; }
public string Customer_Name { get; set; }
public string Shipping_Address { get; set; }
public DateTime Order_Date { get; set; }
public DateTime Modified_Date { get; set; }
public bool Flag { get; set; }
public List<Product> ProductDetails { get; set; }
}
And I want to save data Order details and my piece of code is as follows.
public Order Add(Order odrerDetails)
{
using (var context = new EcommerceDBContext())
{
var MyOrder_Id = Helper.Random(7); //Generate random orderID from my class
foreach (var detail in odrerDetails.ProductDetails)
{
odrerDetails.Order_Id = MyOrder_Id;
odrerDetails.Quantity = Convert.ToInt32(detail.Quantity);
odrerDetails.Amount = Convert.ToDouble(detail.Price);
//Other Details
context.objOrderListing.Add(odrerDetails);
}
context.SaveChanges();
return odrerDetails;
}
}
This gives me perfect data but when it comes to context.SaveChanges(); it return's me error.
An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types.
To me you domain model seems all wrong. The order should just be used for grouping, its a typical e-commerce scenario.
When you get a receipt of your purchases, you get one receipt with every Item and price listed next to it. Its considered as one order of multiple things, not multiple orders of multiple things.
Reading your last comment, you cant have multiple orders with the same order id. Try to understand the domain first before trying to solve it with code. Also,you have no notion of a Customer with an Order.
public class Product
{
[Key]
public int Id { get; set; }
public string SKU_Code { get; set; }
public string Product_Name { get; set; }
public string Price { get; set; }
public string Image { get; set; }
public DateTime Created_Date { get; set; }
public DateTime Modified_Date { get; set; }
}
public class Order
{
[Key]
public long ID { get; set; }
public string Order_Id { get; set; }
public string Payment_Type { get; set; }
public string Customer_Name { get; set; }
public string Shipping_Address { get; set; }
public DateTime Order_Date { get; set; }
public DateTime Modified_Date { get; set; }
public bool Flag { get; set; }
public List<OrderLineItem> Items { get; set; }
}
public class OrderLineItem
{
[Key]
public long ID { get; set; }
public long Order_Id { get; set; }
public long Product_Id {get; set;}
public int Quantity {get; set;}
}

How to gain access to table data through foreign key reference?

I have a statement in one of my entities which uses a foreign key to return an IEnumerable<CustomField>.
I have used LINQ in my repository to test the below method to see if it works and it does. But when I use the foreign key reference in the entity it returns null. Am I missing something here? How can I use a foreign key to gain access to the data in another entity.
Invoice entity:
[Table("vwinvoice")]
public class Invoice
{
[Key]
[DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)]
public int Sys_InvoiceID { get; set; }
[DisplayName("Inc.In Turnover")]
public bool Turnover { get; set; }
public int FK_StatusID { get; set; }
[DisplayName("Invoice No.")]
public string InvoiceNumber { get; set; }
[DisplayName("Invoice Date")]
public DateTime InvoiceDate { get; set; }
[DisplayName("Document Type")]
public string DocType { get; set; }
[DisplayName("Supplier Invoice No.")]
[Column("SupplierInvoiceNumber")]
public string SuppInvNumber { get; set; }
public int FK_SupplierID { get; set; }
[DisplayName("Account Number")]
public string AccountNumber { get; set; }
[DisplayName("Order Number")]
public string OrderNumber { get; set; }
[DisplayName("Order Date")]
public DateTime? OrderDate { get; set; }
[DisplayName("Currency Code_Doc")]
public string CurrencyCode_Doc { get; set; }
[DisplayName("Net Amount_Doc")]
public decimal? NetAmount_Doc { get; set; }
[DisplayName("VAT Amount_Doc")]
public decimal? VATAmount_Doc { get; set; }
[DisplayName("Gross Amount_Doc")]
[Required]
public decimal? GrossAmount_Doc { get; set; }
[DisplayName("Currency Code_Home")]
public string CurrencyCode_Home { get; set; }
[DisplayName("Net Amount_Home")]
public decimal? NetAmount_Home { get; set; }
[DisplayName("VAT Amount_Home")]
public decimal? VATAmount_Home { get; set; }
[DisplayName("Gross Amount_Home")]
public decimal? GrossAmount_Home { get; set; }
[DisplayName("Payment Reference")]
public string PaymentReference { get; set; }
[DisplayName("Supplier")]
public string AccountName { get; set; }
[DisplayName("Status")]
public string StatusName { get; set; }
[DisplayName("Auditor Comments")]
public string AuditorComments { get; set; }
[DisplayName("Reviewer Comments")]
public string ReviewerComments { get; set; }
[DisplayName("Data Source")]
[Required]
public string DataOrigin { get; set; }
public int DetailLineCount { get; set; }
public IEnumerable<CustomField> ClientData {
get {
//Use the CustomFields foreign key to gain access to the data returns null.
return GetCustomFieldData(this.CustomFields.Select(r => r));
}
}
private IEnumerable<CustomField> GetCustomFieldData(IEnumerable<Entities.CustomFields> enumerable) {
return (from f in enumerable
select new CustomField {
Name = f.FK_CustomHeader,
Value = f.Value
});
}
//Custom Field Additions
public virtual ICollection<CustomFields> CustomFields { get; set; }
}
CustomFields entity:
[Table("tblCustomFields")]
public class CustomFields
{
[Key]
public int ID { get; set; }
public int? FK_SysInvoiceID { get; set; }
[StringLength(255)]
public string FK_CustomHeader { get; set; }
[StringLength(255)]
public string Value { get; set; }
public virtual Invoice Invoices { get; set; }
public virtual CustomFieldHeaders CustomFieldHeaders { get; set; }
}
I also cannot place a breakpoint in the get statement to see what happens, why is this? It just skips over the breakpoint whenever I try to return a list of Invoices, which can be seen here:
public IQueryable<Invoice> Invoices
{
get
{
var x = _ctx.Invoices.ToList();
return _ctx.Invoices;
}
}
You are using the virtual keyword when declaring your CustomFields property. As such it will be lazy loaded. If you want the property to be populated once returned from the repository you will need to explicitly Include the table in your method:
var x = _ctx.Invoices.Include(i => i.CustomFields).ToList();
return _ctx.Invoices;
Or you can remove the virtual keyword and the property will always be populated, with the consequent performance hit of the database join and the extra data being returned whenever you access Invoices.

How to query IDBSet via linq

I have a table that i am attempting to query in order to create a menu. I am also querying the related tables to pair down result. I have a models project that contains all of my data models. In my Entities file I have
public IDbSet<Agent> Agents { get; set; }
public IDbSet<UsersLogin> UsersLogins { get; set; }
public IDbSet<Role> Roles { get; set; }
public IDbSet<UserRoleMapping> UserRoleMappings { get; set; }
public IDbSet<Qualifier> Qualifiers { get; set; }
public IDbSet<tblMenus> tblMenu { get; set; }
public IDbSet<tblUserMenuMapping> tblUserMenuMappings { get; set; }
public IDbSet<tblRoleMenuMapping> tblRoleMenuMappings { get; set; }
In my Interface i have ICollection<tblMenus> GetAllMenus();
Then i have my linq query which pares everything down and returns main menus and child menus.
public ICollection<tblMenus> GetAllMenus()
{
if (Global.CurrentProfile.UserID == 1)
{
return DataAccess.tblMenu.Where(m => !m.IsDeleted).ToList();
}
else
{
var UserInfo = GetUserInfo();
UserType = UserInfo.First().UserTypeID;
var childRoleMenus =
from menus in DataAccess.tblMenu
join roleMenus in DataAccess.tblRoleMenuMappings on menus.MenuID equals roleMenus.MenuID
join userRoles in DataAccess.UserRoleMappings on roleMenus.RoleID equals userRoles.RoleID
where userRoles.UserID == Global.CurrentProfile.UserID && !menus.IsDeleted
select menus;
var userChildMenus =
from menus in DataAccess.tblMenu
join userMenus in DataAccess.tblUserMenuMappings on menus.MenuID equals userMenus.MenuID
where userMenus.UserID == Global.CurrentProfile.UserID
select menus;
var childMenus = childRoleMenus.Union(userChildMenus).ToList();
However when i execute the query in my page it returns this error.
The specified type member 'MenuID' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported
Here are my models.
public class tblMenus : ModelBase
{
public int MenuID { get; set; }
public string MenuName { get; set; }
public string MenuLink { get; set; }
public Nullable<int> ParentID { get; set; }
public Nullable<bool> IsParent { get; set; }
public string IconImagePath { get; set; }
public Nullable<int> ApplicationID { get; set; }
public int CreatedBy { get; set; }
public System.DateTime CreatedOn { get; set; }
public string UpdatedBy { get; set; }
public Nullable<System.DateTime> UpdatedOn { get; set; }
public bool IsDeleted { get; set; }
public string ProcessedPage { get; set; }
public string MenuTarget { get; set; }
public Nullable<bool> IsEnabled { get; set; }
public string MenuCategory { get; set; }
public int MenuOrder { get; set; }
public virtual ICollection<tblRoleMenuMapping> tblRoleMenuMapping { get; set; }
public int RoleMenuID { get; set; }
public int RoleID { get; set; }
public int MenuID { get; set; }
public int CreatedBy { get; set; }
public System.DateTime CreatedOn { get; set; }
public Nullable<int> UpdatedBy { get; set; }
public Nullable<System.DateTime> UpdatedOn { get; set; }
public Nullable<bool> IsDeleted { get; set; }
public string ProcessedPage { get; set; }
public string PageAccessibility { get; set; }
public virtual ICollection<tblMenus> tblMenus { get; set; }
public virtual ICollection<Role> Role { get; set; }
public class tblUserMenuMapping : ModelBase
{
public int UserMenuID { get; set; }
public int UserID { get; set; }
public int MenuID { get; set; }
public Nullable<int> CreatedBy { get; set; }
public Nullable<System.DateTime> CreatedOn { get; set; }
public Nullable<int> UpdatedBy { get; set; }
public Nullable<System.DateTime> UpdatedOn { get; set; }
public bool IsDeleted { get; set; }
It's hard to say for sure without seeing the whole of both model classes and your database. Some things to check are:
Verify each respective 'MenuID' column exist in each underlying table. Because you aren't using mapping configurations, you need to make sure the column names follow the convention naming EF expects.
Verify their is a foreign key relationship between the two tables.
From a more general perspective, I would consider using configuration classes so your relationships are explicit and your model is more easily changed from the tables they map to.
Finally, you may see some clues by inspecting the SQL that EF has generated. Use the technique described in this post for any red flags (like EF is looking for a column that doesn't exist):
var result = from x in appEntities
where x.id = 32
select x;
var sql = ((System.Data.Objects.ObjectQuery)result).ToTraceString();

Large data object - am I worrying too much

I work with EF for the first time so I don't know is situation like this normal or I have serious performance issues.
I have following situation:
Bellow are the classes that I have. Item is the main object here. So when I pull a list of Items from database I get for example 1000 items. And now each of this item has all properties filed with data. City contains Country, Country contains list of cities, User has list of created items, each item all data again, city, city has country, country list of cities etc etc...
Maybe I am worrying too much and I don't know should this object's contain all those data and does this make performance issues, or I am doing something wrong here?
public abstract class Item
{
[Key]
public int ItemId { get; set; }
public int ItemTypeId { get; set; }
public Guid UserId { get; set; }
public DateTime CreatedOnDate { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public int? MediaId { get; set; }
public int CityId { get; set; }
public virtual City City { get; set; }
public virtual User User { get; set; }
public virtual ICollection<ItemInBoard> ItemsInBoard { get; set; }
public virtual ICollection<Like> Likes { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class City
{
public int CityId { get; set; }
public string Name { get; set; }
public double Longitude { get; set; }
public double Latitude { get; set; }
public int CountryId { get; set; }
public virtual Country Country { get; set; }
}
public class Country
{
public int CountryId { get; set; }
public string Name { get; set; }
public string CountryCode { get; set; }
public virtual ICollection<City> Cities { get; set; }
}
public class User
{
public Guid UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool Gender { get; set; }
public DateTime? BirthDay { get; set; }
public string AboutMe { get; set; }
public int? MediaId { get; set; }
public int CityId { get; set; }
public virtual City City { get; set; }
public virtual ICollection<Item> Items { get; set; }
public virtual ICollection<Board> Boards { get; set; }
public virtual ICollection<Like> Likes { get; set; }
}
It is up to you. This is a concept called lazy loading. You can enable or disable lazy loading with this code:
context.Configuration.LazyLoadingEnabled = false;
context.Configuration.LazyLoadingEnabled = true;
When enabling this option none of the dependent entities will be loaded. To enforce dependent entities to load you can use the Include lambada expression like this:
var test = context.Tests.Include("SomeOtherDependentEntity");
Hope I got you and this is what you meant.
I would say that what you have is fine for general business logic.
When I have to do a lot of time sensitive processing in a read-only fashion I use SQL commands like this to get exactly and only exactly what I want.
public class myQueryClass
{
public string Property1 { get; set; }
public string Property2 { get; set; }
}
var context = new MyDbContext();
context.Database.SqlQuery<myQueryClass>("SELECT Property1 = acolumn, Property2 = acolumn2 FROM myTable WHERE something = somestate");

Categories

Resources