How to fix EF Core migration error on join table configuration - entity-framework-core

Using EF core 6.0.1, I'm following the example shown here for configuring the join table for a many-to-many relationship in a migration, but I can't get past this error:
The seed entity for entity type 'Classifier' cannot be added because
no value was provided for the required property 'Id'.
modelBuilder.Entity<Classifier>().HasData(
new Classifier
{
Id = 1,
Name = "Concerto",
}
);
modelBuilder.Entity<Composition>()
.HasData(
new Composition
{
Id = -1,
Name = "First Composition",
CreatorId = -1,
LastModifierId = -1,
CreatedDate = DateTime.Parse("01/01/2019"),
LastModifiedDate = DateTime.Parse("01/01/2019"),
Summary = "Lorem ipsum",
}
);
modelBuilder.Entity<Classifier>()
.HasMany(cl => cl.Compositions)
.WithMany(cm => cm.Classifiers)
.UsingEntity(j => j.ToTable("ClassifierCompositions")
)
.HasData(
new { ClassifiersId = 1, CompositionsId = -1, },
);
I've verified that the names used in the anonymous type used to configure the join match the autogenerated column names from EF. I suspect the error message is a poor representation of the real error, since clearly the Classifier.Id is provided. Why is this error being thrown?
Fwiw, the Composition table exists already as created by a previous migration and this migration is adding the Classifier table and join table.

I suspect the error message is a poor representation of the real error, since clearly the Classifier.Id is provided. Why is this error being thrown?
Actually the error is correct. It's because here
modelBuilder.Entity<Classifier>()
.HasMany(cl => cl.Compositions)
.WithMany(cm => cm.Classifiers)
.UsingEntity(j => j.ToTable("ClassifierCompositions")
) // <-- the problem
.HasData(
new { ClassifiersId = 1, CompositionsId = -1, },
);
you are executing the UsingEntity call, which returns EntityTypeBuilder<Classifier>, so the next HasData call is actually defining more Classifier data entries via anonymous type which of course has no Id property, hence the error message.
The correct action of course is to define the seed data for the join entity, using its entity builder inside the UsingEntity call, i.e.
modelBuilder.Entity<Classifier>()
.HasMany(cl => cl.Compositions)
.WithMany(cm => cm.Classifiers)
.UsingEntity(j => j
.ToTable("ClassifierCompositions")
.HasData(
new { ClassifiersId = 1, CompositionsId = -1, },
)
);

Related

EF Core: Get entities matching a filter, but only those that have the latest datetime property in their grouping by a composite key

I have a table of entities of class Pricing.
Previously the Pricing table had a unique index based on 3 foreign keys: InstitutionId, SubmissionTypeId and FeeTypeId.
Now it needs to have multiple possible Pricing rows per that same index, but I should only use the one with the latest value of a DateTime column ActiveFrom, i.e. I should only use the latest active Pricing for the given composite index.
My repository method needs to return all currently active Pricings matching two foreign key values (InstitutionId and SubmissionTypeId).
I tried doing it like this:
DateTime today = DateTime.Today;
IQueryable<Pricing> filteredActivePricings = Context.Pricings
.Where(pricing =>
pricing.InstitutionId == institutionId
&& pricing.SubmissionTypeId == submissionTypeId
&& pricing.ActiveFrom <= today
)
.GroupBy(pricing => new { pricing.InstitutionId, pricing.SubmissionTypeId, pricing.FeeTypeId })
.Select(pricingGroup => pricingGroup
.OrderByDescending(pricing => pricing.ActiveFrom)
.First()
);
IList<Pricing> result = await filteredActivePricings.ToListAsync();
But EF Core says it can't convert that syntax into a valid SQL query.
I've found a solution using pure SQL code, but I'd like to do it in the EF Core's LINQ method syntax, since the rest of my codebase is written in it.
So, I figured out my problem and a solution.
The problem was, as #ivan-stoev pointed out, I wrongly thought that IGrouping<K, T> (the result of IQueryable<T>.GroupBy) contained a collection of T entities grouped by the key K, and that I had to somehow process that collection to get my single entity T.
So here's my solution which utilizes the IQueryable<T>.GroupBy method properly, to get a collection of key values and dates which are then used in a Join back to the table, to get the full data of the entities I need.
DateTime today = DateTime.Today;
IQueryable<ActivePricingKey> filteredActivePricingKeyQuery = Context.Pricings
.Where(pricing =>
pricing.InstitutionId == institutionId
&& pricing.SubmissionTypeId == submissionTypeId
&& pricing.ActiveFrom <= today
)
.GroupBy<Pricing, PricingKey, ActivePricingKey>(
(Pricing pricing)
=> new PricingKey
{
InstitutionId = pricing.InstitutionId,
SubmissionTypeId = pricing.SubmissionTypeId,
FeeTypeId = pricing.FeeTypeId
},
(PricingKey key, IEnumerable<Pricing> pricings)
=> new ActivePricingKey
{
InstitutionId = key.InstitutionId,
SubmissionTypeId = key.SubmissionTypeId,
FeeTypeId = key.FeeTypeId,
ActiveFrom = pricings.Max(pricing => pricing.ActiveFrom)
}
);
IQueryable<Pricing> filteredActivePricingQuery = filteredActivePricingKeyQuery
.Join(
Context.Pricings,
(ActivePricingKey activePricingKey)
=> new
{
activePricingKey.InstitutionId,
activePricingKey.SubmissionTypeId,
activePricingKey.FeeTypeId,
activePricingKey.ActiveFrom
},
(Pricing pricing)
=> new
{
pricing.InstitutionId,
pricing.SubmissionTypeId,
pricing.FeeTypeId,
pricing.ActiveFrom
},
(ActivePricingKey activePricingKey, Pricing pricing) => pricing
);
ICollection<Pricing> pricings = await filteredActivePricingQuery.ToListAsync();

GroupJoin: exception thrown: System.InvalidOperationException

I'm trying to write a query to get all restaurant tables if exists or not a opened sale on it.
if a sale exists on a table I want to get the sum and couple details.that is my code:
db.SALETABLES
.GroupJoin(
db.SALES.Where(c => c.CLOSEDTIME == null),
t => t.ID,
sa => sa.ID_TABLE,
(ta, s) => new
{
ta.ID,
ta.DESCRIPTION,
NR_SALE = s.Any() ? s.First().NR_SALE : 0,
IDSALE = s.Any() ? s.First().ID : 0,
IDUSER = s.Any() ? s.First().IDUSER : 0,
USERNAME = s.Any() ? s.First().USERS.USERNAME :"" ,
SALESUM = s.Any() ? s.First().SALES_DETAIL.Sum(p => p.PRICE * p.CANT) : 0
}
but got this error:
Exception thrown: 'System.InvalidOperationException' in
System.Private.CoreLib.dll
thanks for any help
You don't specify the exception, but I assume it's about client-side evaluation (CSE), and you configured EF to throw an exception when it occurs.
It may be First() that triggers CSE, or GroupJoin. The former can easily be fixed by using FirstOrDefault(). The GroupJoin has more to it.
In many cases it isn't necessary to use GroupJoin at all, of Join, for that matter. Usually, manually coded joins can and should be replaced by navigation properties. That doesn't only make the code better readable, but also avoids a couple of issues EF 2.x has with GroupJoin.
Your SaleTable class (I'm not gonna follow your database-driven names) should have a property Sales:
public ICollection<Sale> Sales { get; set; }
And if you like, Sale could have the inverse navigation property:
public SaleTable SaleTable { get; set; }
Configured as
modelBuilder.Entity<SaleTable>()
.HasMany(e => e.Sales)
.WithOne(e => e.SaleTable)
.HasForeignKey(e => e.SaleTableId) // map this to ID_TABLE
.IsRequired();
Now using a table's Sales property will have the same effect as GroupJoin —a unique key, here a SaleTable, with an owned collection— but without the issues.
The next improvement is to simplify the query. In two ways. 1. You repeatedly access the first Sale, so use the let statement. 2. The query is translated into SQL, so don't worry about null references, but do prepare for null values. The improved query will clarify what I mean.
var query = from st in db.SaleTables
let firstSale = st.Sales.FirstOrDefault()
select new
{
st.ID,
NrSale = (int?)firstSale.NrSale ?? 0,
IdSale = (int?)firstSale.ID ?? 0,
...
SalesSum = (int?)firstSale.SalesDetails.Sum(p => p.Price * p.Cant) ?? 0
}
Using NrSale = firstSale.NrSale, would throw an exception for SaleTables without Sales (Nullable object must have a value).
Since the exception is by the EF Core infrastructure, apparently you are hitting current EF Core implementation bug.
But you can help EF Core query translator (thus avoiding their bugs caused by missing use cases) by following some rules when writing your LINQ to Entities queries. These rules will also eliminate in most of the cases the client evaluation of the query (or exception in EF Core 3.0+).
One of the rules which is the origin of issues with this specific query is - never use First. The LINQ to Objects behavior of First is to throw exception if the set is empty. This is not natural for SQL which naturally supports and returns NULL even for values which normally do not allow NULL. In order to emulate the LINQ to Objects behavior, EF Core has to evaluate First() client side, which is not good even if it works. Instead, use FirstOrDefault() which has the same semantics as SQL, hence is translated.
To recap, use FirstOrDefault() when you need the result to be a single "object" or null, or Take(1) when you want the result to be a set with 0 or one elements.
In this particular case, it's better to incorporate the 0 or 1 related SALE rule directly into the join subquery, by removing the GroupJoin and replacing it with SelectMany with correlated Where. And the Any() checks are replaced with != null checks.
With that said, the modified working and fully server translated query looks like this:
var query = db.SALETABLES
.SelectMany(ta => db.SALES
.Where(s => ta.ID == s.ID_TABLE && s.CLOSEDTIME == null).Take(1), // <--
(ta, s) => new
{
ta.ID,
ta.DESCRIPTION,
NR_SALE = s != null ? s.NR_SALE : 0,
IDSALE = s != null ? s.ID : 0,
IDUSER = s != null ? s.IDUSER : 0,
USERNAME = s != null ? s.USERS.USERNAME : "",
SALESUM = s != null ? s.SALES_DETAIL.Sum(p => p.PRICE * p.CANT) : 0
});

How to do a search with EF CodeFirst

Currently, to do a search using EF CodeFirst and a repository pattern, based on user input to multiple text boxes on an mvc search view/page, I do something like the following:
public PagedList<Entity1> PlayerUserSearch(Entity1SearchParameters searchParameters, int? pageSize, int? startEntity, Func<Entity1, object> sortOrder, bool sortDesc)
{
IQueryable<Entity1> query = from entities in this.DataContext.Entity1s.Include("Entity2List")
where entities.Entity2List.Any()
select entities;
if (searchParameters.Entity2PrimaryKeyId.HasValue)
query = query.Where(e => e.Id == searchParameters.Entity2PrimaryKeyId.Value);
if (searchParameters.HasStats.HasValue)
{
if (searchParameters.HasStats.Value)
query = query.Where(u => u.Entity2List.Any(e => e.Stat != null));
else
query = query.Where(u => u.Entity2List.Any(e => e.Stat == null));
}
if (searchParameters.Entity2OtherField.HasValue)
query = query.Where(u => u.Entity2List.Any(e => e.Event.Entity2OtherField == searchParameters.Entity2OtherField));
if (searchParameters.Entity2OtherField2.HasValue)
query = query.Where(u => u.Entity2List.Any(e => e.Event.Entity2OtherField2 == searchParameters.Entity2OtherField2));
if (searchParameters.Active.HasValue)
query = query.Where(e => e.Active == searchParameters.Active.Value);
return this.GetPageByStartEntity(pageSize.Value, startEntity.Value, query, sortOrder, sortDesc);
}
The problem with this is that for every time I add on another where that checks the child of Entity1 (Entity2) for a certain field, it takes on a new " AND EXISTS" clause to the sql statement generated, so that it is doing an exists and checking table Entity2 all over again for every different field checked, rather than doing a single EXISTS on Entity in the query, and checking all fields I tacked on to the query (i.e. EntityOtherField1 and EntityOtherField2). I haven't been able to find a better way to do a search based on user inputs than constantly checking for the input being there (add to the search parameters)) and then tacking on a new where to the current query. Can anyone tell me if there is a better way to do this? Thanks!
I think what you're looking for is using Specification Pattern.
var spec = new specification<Entity2>(s => true);
if (searchParameters.HasStats.Value)
{
spec = spec.And(e => e.Stat != null);
}
if (searchParameters.Entity2OtherField2.HasValue)
{
spec = spec.And(e => e.Event.Entity2OtherField2 == searchParameters.Entity2OtherField2);
}
query = query.Where(u => u.Entity2List.Any(spec));
Or I believe you can make it more standard by separating the filter logic and using spec.IsStatisfiedBy method.
A good framework for repository/specification implemetation on Entity Framework can be found here.

Join 2 different entities from 2 different models into a single Linq to Entities query

I have a default Entity Framework model that holds all of my default tables for my product, and that all customers share in common. However, on some customers, I have some custom tables that exist for only that customer, but they relate to the default product's tables. I have a second Entity Framework model to hold these custom tables.
My question is how can I make a Linq to Entities query using Join so I can relate the entities from my default model to the tables on my custom model? I don't mind not having the Navigation properties from the custom entity to the entities on the default model; I just need a way to query both models in a single query.
Below is the code:
using (ProductEntities oProductDB = new ProductEntities())
{
using (ProductEntitiesCustom oProductCustomDB = new ProductEntitiesCustom())
{
var oConsulta = oProductCustomDB.CTBLCustoms
.Where(CTBLCustoms => CTBLCustoms.IDWOHD >= 12)
.Join(oProductDB.TBLResources,
CTBLCustoms => new
{
CTBLCustoms.IDResource
},
TBLResources => new
{
TBLResources.IDResource
},
(CTBLCustoms, TBLResources) => new
{
IDCustom = CTBLCustoms.IDCustom,
Descricao = CTBLCustoms.Descricao,
IDWOHD = CTBLCustoms.IDWOHD,
IDResource = CTBLCustoms.IDResource,
ResourceCode = TBLResources.Code
});
gvwDados.DataSource = oConsulta;
}
}
I get a The specified LINQ expression contains references to queries that are associated with different contexts error.
EDIT
Could I merge the 2 ObjectContext into a third, and then run the Linq query?
Tks
EDIT
Below is the code that worked, using the AsEnumerable() proposed solution:
using (ProductEntities oProductDB = new ProductEntities())
{
using (ProductEntitiesCustom oProductCustomDB = new ProductEntitiesCustom())
{
var oConsulta = (oProductCustomDB.CTBLCustoms.AsEnumerable()
.Where(CTBLCustoms => CTBLCustoms.IDWOHD >= 12)
.Join(oProductDB.TBLResources,
CTBLCustoms => new
{
CTBLCustoms.IDResource
},
TBLResources => new
{
TBLResources.IDResource
},
(CTBLCustoms, TBLResources) => new
{
IDCustom = CTBLCustoms.IDCustom,
Descricao = CTBLCustoms.Descricao,
IDWOHD = CTBLCustoms.IDWOHD,
IDResource = CTBLCustoms.IDResource,
ResourceCode = TBLResources.Code
})).ToList();
gvwDados.DataSource = oConsulta;
}
}
I added the AsEnumerable() as suggested, but I had to add the ToList() at the end so I could databind it to the DataGridView.
You can't do this in L2E. You could bring this into object space with AsEnumerable(), and it would work, but possibly be inefficient.
Merging the ObjectContexts is possible, and would work, but would need to be done manually.

Entity Frmwk pb: search for 'added' entities in current context

I'm using guids as PK for my entities.
As EF doesn't support the newid() SQL statement, I force a Guid.NewGuid() in my creation methods.
Here is my problem :
I've a table with a clustered unique constraint (2 strings, not PK).
I'm running some code in the same EF context which perfoms operations and adds/links entities, etc.
Is it possible to search for an entity in 'Added' state in my context ? ; that is to say which is in my context, but not yet inserted in my DB.
To avoid the raising of the SQL unique constraint, I have to know if the entity is already 'queued' in the context, and to re-use it instead of creating a new Guid (... and a different entity! :( )
this post saved me :) :
Link
var stateEntries = context.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EtityState.Modified | EntityState.Unchanged);
var roleEntityEntries = stateEntries.Select(s => s.Entity).OfType<Role>();
roleEntity = roleEntityEntries.FirstOrDefault(r => r.RoleName.Trim().ToLower() == roleName.Trim().ToLower());
if (roleEntity == null)
{
return new Role { RoleName = roleName };
}