EF7 generating incorrect sql with optional relationships - asp.net

Consider the following models:
public class News
{
public int Id { get; set; }
public string Title { get; set; }
public Branch Branch { get; set; }
public int? BranchId { get; set; }
}
public class NewsDto
{
public int Id { get; set; }
public string Title { get; set; }
public BranchDto Branch { get; set; }
}
public class Branch
{
public int Id { get; set; }
public string Name { get; set; }
public List<News> News { get; set; } = new List<News>();
}
public class BranchDto
{
public int Id { get; set; }
public string Name { get; set; }
}
Here we have an optional relationship between News and Branch (1-to-many).
We're issuing the following query:
context.News
.Select(dto => new NewsDto()
{
Id = dto.Id,
Title = dto.Title,
Branch = dto.Branch == null ? null : new BranchDto()
{
Id = dto.Branch.Id,
Name = dto.Branch.Name
}
}).ToList();
EF6
The generated query is:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Title] AS [Title],
CASE WHEN ([Extent2].[Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1],
[Extent1].[BranchId] AS [BranchId],
[Extent2].[Name] AS [Name]
FROM [dbo].[News] AS [Extent1]
LEFT OUTER JOIN [dbo].[Branches] AS [Extent2] ON [Extent1].[BranchId] = [Extent2].[Id]
This is how it should be: a left outer join after a CASE WHEN to handle null references to the Branches table.
EF7 (RC1)
The same query is generated as:
SELECT
[dto].[Id],
[dto].[Title],
[dto].[BranchId] IS NULL,
[dto].[BranchId],
[dto.Branch].[Name]
FROM [News] AS [dto]
INNER JOIN [Branch] AS [dto.Branch] ON [dto].[BranchId] = [dto.Branch].[Id]
I don't really understand why this is happening. The relationship is clearly optional so inner joins are just plain wrong. Other than that, the line [dto].[BranchId] IS NULL is giving a System.Data.SqlClient.SqlException: Incorrect syntax near the keyword 'IS' so I guess this isn't even valid sql.
This is only happening when the relationship is optional. What's going on?

So I believe this is related to issue #3186. The EF team recognizes this as a high priority bug and it should be fixed in RC2.

Related

Entity Framework - LeftJoin on multiple columns

I'm trying to write in C# a query with left join using Lambda Expression.
How can i get the wanted SQL query when i have a filter in the joined table.
I mean without subquery, with multiple verifications on 'ON' statement
public class Article
{
public int Id { get; set; }
public string ArticleName { get; set; }
// EF navigation property
public ICollection<UserArticleNames> UserArticleNames { get; set; }
}
public class User
{
public int Id { get; set; }
public string UserName { get; set; }
}
public class UserArticleName
{
// FK Users
public int UserId { get; set; }
// FK Articles
public int ArticleId { get; set; }
public int UserArticleName { get; set; }
}
EF - LINQ query via navigation property: (get Articles for an user)
var join = context.Articles
.SelectMany(a => a.UserArticleNames
.Where(ua => ua.UserId == 1)
.DefaultIfEmpty(), (a, ua) => new {
Id = a.Id,
ArticleName = a.ArticleName ,
UserArticleName = ua.UserArticleName,
}
);
Resulting SQL code:
SELECT [a].[Id],
[a.ArticleName],
[ua].[UserArticleName]
FROM [dbo].[Articles] AS [a]
LEFT JOIN (
SELECT [a0].[UserId],
[a0].[ArticleId],
[a0].[UserArticleName],
FROM [dbo].[UserArticleName] AS [a0]
WHERE [a0].[UserId] = 1
) AS [ua] ON [a].[Id] = [ua].[Id]
Wanted SQL code:
SELECT [a].[Id],
[a.ArticleName],
[ua].[UserArticleName]
FROM [dbo].[Articles] AS [a]
LEFT JOIN [dbo].[UserArticleName] AS [ua]
ON [a].[Id] = [ua].[Id] AND [ua].[UserId] = 1

SQLiteNetExtensions - InsertWithChildrenAsync not working when Foreign Key constraint exists on child table

Xamarin.Forms project, using SQLiteNetExtensions 2.0.0 and sqlite-net-pcl 1.5.231
I am trying to insert new records into a sqlite database. My scenario works when I drop the NOT NULL and Foreign Key constraint on the child table. When I re-add the constraints I get the following exception:
SQLite.SQLiteException: 'Constraint'
Parent Model
[Table("RetailItem")]
public class RetailItemDTO
{
[PrimaryKey, AutoIncrement]
public int RetailItemId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool Taxable { get; set; }
public int DepartmentId { get; set; }
public int RetailTypeId { get; set; }
[OneToMany(CascadeOperations = CascadeOperation.All)]
public List<RetailItemRetailerPriceDTO> Prices { get; set; }
}
Child Model
[Table("RetailItemRetailerPrice")]
public class RetailItemRetailerPriceDTO
{
[PrimaryKey, AutoIncrement]
public int RetailItemRetailerPriceId { get; set; }
[ForeignKey(typeof(RetailItemDTO)), NotNull]
public int RetailItemId { get; set; }
public int RetailerId { get; set; }
public decimal Price { get; set; }
}
Insert/Update logic
public async Task Save(RetailItem entity)
{
var dto = GetDTOFromBusinessModel(entity);
if (dto.RetailItemId == 0)
{
await _sqliteRepository.Database.InsertWithChildrenAsync(dto, true);
entity.Id = dto.RetailItemId;
}
else
await _sqliteRepository.Database.InsertOrReplaceWithChildrenAsync(dto, true);
}
If I have an existing RetailItem, with a newly added price, the InsertOrReplaceWithChildrenAsync call works without issue fails with the following: SQLite.NotNullConstraintViolationException: 'NOT NULL constraint failed: RetailItemRetailerPrice.RetailItemId'
Anytime I add a new RetailItem with a new RetailItemRetailerPrice the Constraint exception occurs.
If I drop the NOT NULL and Foreign Key constraint for the RetailItemId on the RetailItemRetailerPrice table then both calls work.
I want to keep the proper constraints on my table.
Am I missing some attributes that will make my scenario work?
You have set the RetailItemIdnot null ,when you add a new item in RetailItemRetailerPrice table, you must set a RetailItemId for the new price.
[ForeignKey(typeof(RetailItemDTO)), NotNull]
public int RetailItemId { get; set; }
If you do not want to keep the constraints on your table. Before you add a new item in RetailItemRetailerPrice table, you can query the RetailItem table, get the RetailItemId, In the end , you can add this new item in RetailItemRetailerPrice table.

asp.net - LINQ Query with relational Data

I have two tables Category and Document. See relationships in picture
See picture
I wrote the following query to select data from both tables based on relationship
public List<DocumentViewModel> All()
{
var docs = _context.Document.ToList();
List<DocumentViewModel> docList = docs.Select(x => new DocumentViewModel
{ DocumentId = x.DocumentId,
DocumentPath = x.DocumentPath,
CategoryId = x.CategoryId,
CategoryName = x.Category.CategoryName }).ToList();
return docList;
}
when this function is called , I get the following error
System.NullReferenceException: 'Object reference not set to an instance of an object.'
Here are my modals
document
public class Document
{
[Key]
public int DocumentId { get; set; }
[Required]
public string DocumentPath { get; set; }
public Nullable<int> CategoryId { get; set; }
public virtual Category Category { get; set; }
}
Category
public class Category
{
[Key]
public int CategoryId { get; set; }
[Required]
public string CategoryName { get; set; }
public virtual ICollection<Document> Documents { get; set; }
}
DocumentViewModel
public class DocumentViewModel
{
public int DocumentId { get; set; }
public string DocumentPath { get; set; }
public int? CategoryId { get; set; }
public string CategoryName { get; set; }
}
Any Idea where am doing mistake?
In this case there is no reason to get a List in memory and then do the projection, you can do this directly from EF instead. Even if there is no relationship defined EF will return null for CategoryName if you project the the results. If you go to memory first then an NRE is expected if there is no Category relationship.
public List<DocumentViewModel> All()
{
return _context.Document.Select(x => new DocumentViewModel
{ DocumentId = x.DocumentId,
DocumentPath = x.DocumentPath,
CategoryId = x.CategoryId,
CategoryName = x.Category.CategoryName}).ToList();
}
Original reason why it is failing.
There is at least one entity that does not have a corresponding relationship with Category.
You do not have lazy loading enabled (which is a good thing) and if that is the case you should use Include to return the relationship.

EF 5, one to many, more than one table

I'm having a little of trouble with the following classes:
public class TwoVariableDetails
{
public TwoVariableDetails()
{
MovementsPerBlocks = new HashSet<MovementsRow>();
MovementsPerShiftTypes = new HashSet<MovementsRow>();
MovementsPerMachines = new HashSet<MovementsRow>();
MovementsPerShifts = new HashSet<MovementsRow>();
}
[Key]
public Guid TwoVariableDetailsId { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
[Required]
[MaxLength(1000)]
[DataType(DataType.MultilineText)]
public string Description { get; set; }
public virtual ICollection<MovementsRow> MovementsPerBlocks { get; set; }
public virtual ICollection<MovementsRow> MovementsPerShiftTypes { get; set; }
public virtual ICollection<MovementsRow> MovementsPerMachines { get; set; }
public virtual ICollection<MovementsRow> MovementsPerShifts { get; set; }
}
[Table("Movement")]
public class MovementsRow
{
public MovementsRow()
{
MovementsCells = new HashSet<MovementsCell>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[CsvField(Ignore = true)]
public Guid MovementId { get; set; }
[Required]
public int RowNo { get; set; }
[Required]
[CsvField(Ignore = true)]
public Guid ModelId { get; set; }
[ForeignKey("ModelId")]
[CsvField(Ignore = true)]
public virtual TwoVariableDetails Model { get; set; }
[TypeConverter(typeof(MovementsCellTypeConverter))]
public virtual ICollection<MovementsCell> MovementsCells { get; set; }
}
[Table("MovementCell")]
public class MovementsCell
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[CsvField(Ignore = true)]
public Guid CellId { get; set; }
[Required]
public int ColumnNo { get; set; }
[Required]
public int Count { get; set; }
[Required]
[CsvField(Ignore = true)]
public Guid MovementId { get; set; }
[ForeignKey("MovementId")]
[CsvField(Ignore = true)]
public virtual MovementsRow Model { get; set; }
}
When I try to save it to the database I get the following error:
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.Movement_dbo.TwoVariableDetails_ModelId". The conflict occurred in database "aspnet-GreenCranes.UI-20130516", table "dbo.TwoVariableDetails", column 'TwoVariableDetailsId'.
The statement has been terminated.
This is the code I'm using for saving:
twoVariableDetails.TwoVariableDetailsId = Guid.NewGuid();
_context.TwoVariableDetailsModels.Add(twoVariableDetails);
_context.SaveChanges();
My table looks like this:
Movement
- Column
- MovementId
- RowNo
- ModelId(FK, uniqueidentifier, not null)
- TwoVariableDetails_TwoVariableDetailsId(FK, uniqueidentifier, null)
- TwoVariableDetails_TwoVariableDetailsId2(FK, uniqueidentifier, null)
- TwoVariableDetails_TwoVariableDetailsId3(FK, uniqueidentifier, null)
- TwoVariableDetails_TwoVariableDetailsId4(FK, uniqueidentifier, null)
- Keys
- FK_dbo.Movement_dbo.TwoVariableDetails_ModelId
- FK_dbo.Movement_dbo.TwoVariableDetails_TwoVariableDetails_TwoVariableDetailsId
- FK_dbo.Movement_dbo.TwoVariableDetails_TwoVariableDetails_TwoVariableDetailsId1
- FK_dbo.Movement_dbo.TwoVariableDetails_TwoVariableDetails_TwoVariableDetailsId2
- FK_dbo.Movement_dbo.TwoVariableDetails_TwoVariableDetails_TwoVariableDetailsId3
I'm not sure what is the problem with my approach. Should I change the MovementsRow class to have four Model properties and four modelid fk and then use InverseProperty attribute?
MovementsRow.Model belongs to another relationship than the four collections in TwoVariableDetails. That's the reason why you don't have four, but five foreign keys in the database table. When you insert twoVariableDetails into the DB and it contains a MovementRow instance in one of the collections EF expects that its ModelId is set to a Guid that references an existing TwoVariableDetails row - which it doesn't apparently. Hence the exception.
Should I change the MovementsRow class to have four Model properties
and four modelid fk and then use InverseProperty attribute?
I'd say yes. It's probably the best solution. The alternative is to have no Model property at all in MovementRow. It's working but you would not be able to navigate from MovementRow to TwoVariableDetails then.
Your FK_dbo.Movement_dbo.TwoVariableDetails_ModelId is being violated, simply put - the ModelId that the Movement record is using doesn't yet exist in TwoVariableDetails.
If you wanted to keep it simple, and transactional, then you could use TransactionScope along with your database context, save the TwoVariableDetails first in the transaction, and then the records that relate back to it:
using (var context = new MyDbContext())
using (var tranScope = new TransactionScope(TransactionScopeOption.Required) {
// don't save the Movement records yet
twoVariableDetails.TwoVariableDetailsId = Guid.NewGuid();
_context.TwoVariableDetailsModels.Add(twoVariableDetails);
_context.SaveChanges();
// now create the movement records, add them to twoVariableDetails
...
_context.SaveChanges();
// commit the transaction
scope.Complete();
}

LinqPad Query to Visual Studio - how to use a nested query to populate a viewmodel

This is a follow up to an earlier question.
I want to populate a ViewModel, which has 3 properties, and one list of Occ class (which also has 3 properties.
public class RatesViewModel
{
public string TypeName { get; set; }
public long TypeID { get; set; }
public int TypeCount { get; set; }
public virtual IQueryable<Occ> Occs { get; set; }
}
public class Occ
{
public string occ { get; set; }
public decimal ratetocharge { get; set; }
public int numOfOcc { get; set; }
public virtual RatesViewModel RatesViewModel { get; set; }
}
When I run the following Linq query in LinqPad:
var rooms = tblRoom
.GroupBy(p => p.tblType)
.Select(g => new
{
TypeName = g.Key.type_name,
TypeID = g.Key.type_id,
TypeCount = g.Count(),
Occs = rates.Where(rt => rt.type_id == g.Key.type_id &&
(
(rt.type_id == g.Key.type_id)
))
.GroupBy(rt => rt.occ)
.Select(proj => new
{
occ = proj.Key,
ratetocharge = proj.Sum(s => s.rate),
numOfOcc = proj.Count()
})
});
rooms.Dump();
...as before, it correctly returns the data model I'm looking for:
...and when I click on Occs it drills down into the Occs class:
The complete view in LinqPad is:
My query in Visual Studio is:
var rooms = dbr.Rooms
.GroupBy(p => p.RoomTypes).Select(g => new RatesViewModel
{
TypeName = g.Key.type_name,
TypeID = g.Key.type_id,
TypeCount = g.Count()
,
Occs = db.Rates.Where(rt => rt.type_id == g.Key.type_id &&
(
(rt.type_id == g.Key.type_id)
))
.GroupBy(rt => rt.occ)
.Select(proj => new Occ
{
occ = proj.Key,
ratetocharge = proj.Sum(s => s.rate),
numOfOcc = proj.Count()
})
})
.ToList();
However when running this, I get an error:
The specified LINQ expression contains references to queries that are associated with different contexts.
I think I understand the error - but I'm not sure how to separate the query into 2 separate queries, and then join those query results together again to get my original results set.
My model classes are:
public class Rates
{
public int id { get; set; }
public long type_id { get; set; }
public DateTime ratedate { get; set; }
public decimal rate { get; set; }
public string occ { get; set; }
public List<RoomType> Type { get; set; }
}
public class Rental
{
[Key]
public long rental_id { get; set; }
public long room_id { get; set; }
public DateTime check_in { get; set; }
public DateTime check_out { get; set; }
public virtual Room Room { get; set; }
}
public class Room
{
[Key]
public long room_id { get; set; }
public long type_id { get; set; }
public virtual RoomType RoomTypes { get; set; }
public virtual ICollection<Rental> Rentals { get; set; }
}
public class RoomType
{
[Key]
public long type_id { get; set; }
public string type_name { get; set; }
public IQueryable<Rates> Rates { get; set; }
public virtual ICollection<Room> Room { get; set; }
}
Can anyone help either review my query or models, so it works with one query, or show me how to separate the query into two, and then combine the result sets?
Thank you,
Mark
apitest.Models.RoomContext' does not contain a definition for 'Rates'...
(your comment on hydr's answer)
Well, there you go: not only two different context instances but two different context classes. I suspect your linqpad query was directly against the database connection, which means it used one linq-to-sql DataContext (created on the fly).
You need to use one context class (and one instance of it) in your query. And connect to it in Linqpad to make sure you test the same query provider as Visual Studio.
dbr and db seem to be two different instances of the same context. But in one query you should only use one context. So I would suggest the following:
Occs = dbr.Rates.Where(rt => rt.type_id == g.Key.type_id && ....
If this doesn't help can you quote the lines where you initialize the contexts?

Resources