Intersecting elements in Entity Framework entities with Linq - c#

I have an Entity Framework entity in my ASP.NET Core 3.1 MVC project called Tag, which is a simple model:
public class Tag
{
public string TagName { get; set; }
public string TagNameNormalized { get; set; }
public DateTime CreatedAt {get; set; }
public string CreatedBy {get; set; }
}
A user can add tags from the website, and these tags are stored in a ICollection<Tag>. But I want to store only new tags, and not duplicates.
At some point, I need to evaluate the ICollection that holds the newly added tags against the ones already stored in the database (_context). I just want unique names (the TagName property), the other properties can be the same.
Right now, I'm doing this:
ICollection<Tag> duplicateTags = _context.Tags.ToList().Intersect(tagsToCheck).ToList();
but this is probably not doing what I want, because it will evaluate all of the model's properties.
What kind of Linq query should I use, where I evaluate just the TagName property, but still select the whole entity?
Any help and suggestions are appreciated!

You can use linq Join
ICollection<Tag> duplicateTags = _context.Tags.Join(
tagsToCheck,
t => t.TagName, c => c.TagName,
(t,c) => t).ToList()
The above will give the duplicate tag objects.
Another way
var tagsNameToCheck = tagsToCheck.Select(t => t.TagName);
ICollection<Tag> duplicateTags = _context.Tags.Where(
t => tagsNameToCheck.Contains(t.TagName))
.ToList();
Point to notice that _context.Tags.ToList() would bring all tags from database into memory which can be a performance issue in your case if there are numerous tags. Would suggest to go for 2nd solution.

Morgan,
You can use Distinct(). For example:
ICollection<Tag> duplicateTags = _context.Select(t => t.TagName).Distinct().ToList()
Does this answer your question?

Related

Build LINQ query for One-to-One and filtering by child

I have very simple model with two entities related as One-to-One via reference navigation properties:
class Post //"Main"
{
public int RowId { get; set; }
public string SomeInfo { get; set; }
public FTSPost FTSPost { get; set; }
}
class FTSPost //"Child"
{
public int RowId { get; set; }
public Post Post { get; set; }
public string Content { get; set; }
public string Match { get; set; }
}
I'm not sure if it's important but FTSPost represents virtual table of FTS5 SQLite and I'm following this exmaple so FTSPost is used for free text search capability. What I need to do is just to retrieve the whole rows of data from both tables based on the text search result and not just the text itself as in the example. I.e. I'm searching by Content of FTSPost and need to get respective SomeInfo of Post, not just Content itself. Note: Match is the service property which is used for searching and it's bound to the name of FTSPosts table. It works, so I can retrieve just Content as in the example.
The obvious (for me) query doesn't work, it yields zero results:
//Doesn't work! -- zero results :(
results = _context.Posts.Where(m => m.FTSPost.Match == "text for search");
SELECT *
FROM "Posts" AS "m"
LEFT JOIN "FTSPosts" AS "f" ON "m"."RowId" = "f"."RowId"
WHERE "f"."FTSPosts" = "text for search"
However, the following nice raw SQL query works well but I can't wrap my mind how to make it in LINQ! I tried to repeat it as is, with double "from" clauses but it converts to CROSS JOIN and doesn't work either. Please, help! P.S. I use EF Core 5.
//It works!!
SELECT *
FROM "Posts" AS "m", "FTSPosts" AS "f"
WHERE "f"."FTSPosts" = "text for search" AND "m"."RowId" = "f"."RowId"
The reason why "obvious" query didn't work was the way how the one-to-one relation was created. Even though the one-to-one seems to be mirrored, the way matters. After swaping Message and FTSMessage in the following code everything started to work. LEFT JOIN in the generated SQL query was replaced by INNER JOIN.
Correct code:
modelBuilder.Entity<Message>(
x =>
{
x.HasOne(fts => fts.FTSMessage)
.WithOne(p => p.Message)
.HasForeignKey<Message>(p => p.RowId);
});

Entity Framework Core Generating SQL With Ambiguous Column Names

I am using Entity Framework Core 2.2.6. I'm going to try and make this question concise and apologies in advance if it ends up being a wall of text.
The error I am seeing is an ambiguous column name in the SQL Entity Framework Core generates.
So my situation is this: I have two entities with a many-to-one relationship. The "parent" entity implements
an interface that has a property that is of type IChildEntity. Here are the interfaces:
public interface IParentEntity
{
IChildEntity Child { get; set; }
string Prop1 { get; set; }
string Prop2 { get; set; }
}
public interface IChildEntity
{
string ChildProp1 { get; set; }
string ChildProp2 { get; set; }
}
I am using ef core's fluent api and in order to set up the relationship between parent and child
I am using a concrete type of ChildEntity and defining a IChildEntity property to conform to the
interface and just passing things through to the concrete type:
public class ChildEntity : IChildEntity
{
public long ID {get; set;}
public string ChildProp1 { get; set; }
public string ChildProp2 { get; set; }
}
public class ParentEntity : IParentEntity
{
public long ID { get; set; }
public string Prop1 { get; set; }
public string Prop2 { get; set; }
public long ChildID { get; set; }
// Navigation property so EF Core can create the relationship
public ChildEntity MappedChild { get; private set; }
// this is to adhere to the interface
// just pass this through to the backing concrete instance
[NotMapped]
public IChildEntity Child
{
get => MappedChild;
set => MappedChild = (ChildEntity)value;
}
}
Then in OnModelCreating I set up the relationship like so:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ParentEntity>()
.HasOne(e => e.MappedChild)
.WithMany()
.HasForeignKey(e => e.ChildID);
}
This works and the relationship gets set up as expected, however I am finding when I do a query it can generate
some SQL that can result in an ambigous column error in some database engines. Here is the example query:
MyContext.ParentEntity
.Include(p => p.MappedChild)
.Where(p => p.Prop1.Equals("somestring")
.FirstOrDefault()
The SQL that gets generated is similar to:
SELECT p."ID", p."ChildID", p."Prop1", p."Prop1", "p.MappedChild"."ID", "p.MappedChild"."ChildProp1", "p.MappedChild"."ChildProp2"
FROM "ParentEntity" AS p
INNER JOIN "ChildEntity" AS "p.MappedChild" ON p."ChildID" = "p.MappedChild"."ID"
WHERE p."Prop1" = 'somestring'
ORDER BY "p.MappedChild"."ID"
LIMIT 1
The problem here is we are selecting two columns with the name ID and not aliasing. Some databases will be ok with this
but some will not. A work around I can do for this is to do two separate queries to get the entity and the child entity:
var parent = MyContext.ParentEntity
.Where(p => p.Prop1.Equals("somestring")
.FirstOrDefault()
MyContext.Entry(parent).Reference(p => s.MappedChild).Load();
But this is less than ideal since it does multiple queries and is a bit less elegant than just using Include()
Because this seems like such a common use case and I couldn't find any bug reports against EF Core for this type of
behavior it is my suspicion that I am doing something wrong here that is resulting in EFCore not aliasing column names
for this type of query. I was thinking it could be the bit of trickery I have to do to ensure my entity implements it's interface
(this is something I can't due to constraints in the codebase and other integrations) but the more I look at it the less likely that
seems to me since we are directly dealing with the "mapped" property in EF related code and it's completely unaware of the interface.
My questions are - can anyone see something in my implementation that would cause this? Could anyone
suggest a better workaround than what I have here? Any advice here would be appreciated. Thanks much.
This is an old Entity framework bug with the Oracle company products bug including the MySQL database and Oracle database (12.1 and older).
I see the
ORA-00918: column ambiguously defined
error mostly when:
Selecting one entity with including parent entity.
Selecting one entity with value object own one command
This error appears when using Find, First, FirstOrDefault, Last, Single and all single entity selector commands.
I tested many solutions and check generated sql statement to find out a very unique way without any performance overhead:
// This the way of getting one entity from oracle 12.1 without throwing Oracle exception => ORA-00918: column ambiguously defined without any extra overhead
var entities = await dbSet.Where(x => x.Id == id).Take(1).ToListAsync();
var entity = entities.FirstOrDefault();
Another Sample:
var entities = await dbSet.OrderByDescending(x => x.Id).Take(1).ToListAsync();
var entity = entities.FirstOrDefault();
At the end of your IQueryable Linq add Take(1) and get all with .ToList() or .ToListAsync() to execute the statement and fetch a list with one record. Then use Enumerable Single Entity Selector to change the list to an entity.
That’s all.

EF FromSql() with related entities

Disclaimer: these requirements are not set by me, unless this is an impossible task I cannot convince my boss otherwise.
Let's say we have two entities: Item and ItemTranslation.
public class Item
{
public int Id { get; set; }
public string Description { get; set; }
public virtual ICollection<Item> Children { get; set; }
public virtual Item Parent { get; set; }
public virtual ICollection<ItemTranslation> Translations { get; set; }
}
public class ItemTranslation
{
public int Id { get; set; }
public string CultureId { get; set; }
public string Description { get; set; }
public virtual Item Item { get; set; }
}
The requirement is that Item.Description should be filled in based on a language selected by default, but also allowing it to be specified based on what the user wants. The Item.Description column doesn't actually exist in the database.
In SQL this would be easy: all you have to do is query both tables like so
SELECT [Item].[Id], [ItemTranslation].[Description], [Item].[ParentId]
FROM [Item]
LEFT JOIN [ItemTranslation] ON [Item].[Id] = [ItemTranslation].[ItemId]
WHERE [CultureId] = {cultureId}
Or use an OUTER APPLY depending on your implementation. I have added this query to the .FromSql() function built in Entity Framework.
Put this all together in an OData API and this all works fine for one Item. However as soon as you start using $expand (which behind the scenes is a sort of .Include()) it no longer works. The query being sent to the database for the related entities no longer holds the SQL which I specified in .FromSql(). Only the first query does. On top of this when you would query an Item from a different controller e.g. ItemTranslation this would also no longer work since .FromSql() is only applied in the other controller.
I could write a query interceptor which simply replaces the generated SQL by Entity Framework and replaces FROM [Item] with FROM [Item] LEFT JOIN [ItemTranslation] ON [Item].[Id] = [ItemTranslation].[ItemId] WHERE [CultureId] = {cultureId} but I wonder if there is a better implementation than that. Perhaps even a redesign in models. I'm open to suggestions.
FromSql has some limitations. I suspect this is the reason why Include won't work.
But once you use EF, why are you messing with SQL? What difficulties does that query have which prevents you from doing it in LINQ? Left join maybe?
from item in ctx.Items
from itemTranslation in ctx.ItemTranslations.Where(it => it.Item.Id == item.Id).DefaultIfEmpty()
where itemTranslation.CultureId == cultureId
select new { item.Id, itemTranslation.Description, ParentId = item.Parent.Id };
Update
Going over the issue again, I see a further problem. Include will only work on an IQueryable<T> where T is an entity whose navigation properties are mapped properly. Now, from this perspective, it doesn't matter if you use FromSql or LINQ if it produces an IQueryable of some projection instead of an entity, Include won't work for obvious reasons.
To be able to include ItemTranslation entities, your action method should look something like this:
[Queryable]
public IQueryable<Item> GetItems()
{
return db.Items;
}
So the framework can perform $expand on the IQueryable<Item> you return. However, this will include all item translations, not just the ones with the desired culture. If I get it correctly, this is your core issue.
It's obvious as well that you cannot apply this culture filter to an IQueryable<Item>. But you shouldn't do that as this is achieved by $filter in OData:
GET https://.../Items/$expand=Translations&$filter=Translations/CultureId eq culture

LINQ query a list inside a record

EDIT
Turns out EF wont map List strings to a table. So to fix it I've created a simple table with an id, a string field and a foreign Key to the Example table, then in the example table i've updated the List<> attribute to point to this table. ...I think I had a brain fart. sorry all.
I've tried the other questions but I'm still getting the same error.
I have a table with the following:
public class Example
{
public Example()
{
ExampleList = new List<string>();
}
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public List<string> ExampleList{ get; set; }
}
Now when I go to query the database, I want to return back entries that contain "imastring" in their ExampleList I'm currently using:
var test = db.Examples.Where(c => c.ExampleList.Count(z => z.Contains("imastring")) == 0).ToList();
However, this query brings back the following error:
The specified type member 'ExampleList' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
I can't figure out whats wrong, what am i missing guys?
For clarity
I'm trying to bring back all "Example" record whos "ExampleList<>" contains the string "imastring"
You cannot store in database list of string type (or primitives). You need to create for example ExampleTable class with id and string value. Or keep whole list in one property (for example strings separated with ';') and parse given string with split. Or serialize list to json and save as string property.

Using AutoMapper when having a LINQ statement with multiple .Include

So I am new to using AutoMapper and have been able to get basic mapping of items no problem with using LINQ statements that do not use the .Include("blah"), however when I have a statement for example like this;
var courses = dc.Courses.Include("Students")
.Include("CourseTimes")
.OrderBy(n=>n.CourseSemester.courseStart);
AutoMapper doesnt seem to pull any of the information from ("Students") or ("CourseTimes"). My objects are posted below and to give a quick breakdown, Courses contain a List of Students(I need Students so I can count the number of people in each course), Courses also contain a List of CourseTimes(so I can display the times of each class for the given course). Here is my ViewModel that I am using.
public class UserIndexCourseList
{
[Key]
public int courseId { get; set; }
public string courseCode { get; set; }
public string courseName { get; set; }
// this simply stored a count when I did Students.Count without using AutoMapper
public int size { get; set; }
public string room { get; set; }
public List<CourseTime> courseTimeSlot { get; set; }
}
Here are some of the AutoMapper statements I tried to used but had no luck with it working.
//to the viewmodel
Mapper.CreateMap<Models.Course, ViewModels.UserIndexCourseList>();
Mapper.CreateMap<Models.CourseTime, ViewModels.UserIndexCourseList>();
Mapper.CreateMap<Models.Student, ViewModels.UserIndexCourseList>();
//from the viewmodel
Mapper.CreateMap<ViewModels.UserIndexCourseList, Models.Course>();
Mapper.CreateMap<ViewModels.UserIndexCourseList, Models.CourseTime>();
Mapper.CreateMap<ViewModels.UserIndexCourseList, Models.Student>();
So essentially how can I create a Map which will also pull all of that information so I can use it with my ViewModel that was posted above ? I have tried numerous options but no luck.
I apologize for a similar post I made ahead of time but I don't think I explained myself well enough the first time. Thanks again!
By convention automapper maps properties with same names, so in your case you can do this:
public class UserIndexCourseList
{
...
//rename field so it has same name as reference
public List<CourseTime> CourseTimes{ get; set; }
}
or you can rename reference in EF so it's name is courseTimeslot.
Another solution if you don't want to rename your property is to add options to map, for example:
Mapper.CreateMap<Models.Course, ViewModels.UserIndexCourseList>()
.ForMember(d => d.courseTimeSlot,
opt => opt.MapFrom(src => src.CourseTime));
Edit: also they have great documentation, your case is described here: https://github.com/AutoMapper/AutoMapper/wiki/Projection
"Because the names of the destination properties do not exactly match up to the source property (CalendarEvent.Date would need to be CalendarEventForm.EventDate), we need to specify custom member mappings in our type map configuration..."

Categories

Resources