I have a table restaurant. A restaurant can have multiple specialties. And specialties are for example chinees, spahnish, greeks, etc.
Restaurant table/class:
[Table("Restaurant")]
public class Restaurant
{
[Key]
[Column(Order = 0)]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
[MaxLength(40)]
[Column(Order = 2)]
public string Name { get; set; }
[MaxLength(1000)]
[Column(Order = 6)]
public string Comments { get; set; }
public virtual ICollection<Specialties> Specialties { get; set; }
}
And this is my specialties table/class:
public enum Specialty
{
Chinees = 0,
Spanish = 1,
Etc.. = 2,
}
[Table("Specialties")]
public class Specialties
{
[Key]
[Column(Order = 0)]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
[Column(Order = 1)]
public Specialty Specialty { get; set; }
//[Required]
[MaxLength(20)]
[Column(Order = 2)]
public string Name { get; set; }
public virtual ICollection<Restaurant> Restaurant { get; set; }
}
The specialties table is pre-filled with data.
This is my table/class for the many to many relationship:
[Table("RestaurantSpecialties")]
public class RestaurantSpecialties
{
[Key]
public int RestaurantId { get; set; }
[Key]
public int SpecialtyId { get; set; }
[ForeignKey("RestaurantId")]
public virtual Restaurant Restaurant{ get; set; }
[ForeignKey("SpecialtyId")]
public virtual Specialties Specialty { get; set; }
}
With Fluent Api I tried to define the model like this:
// Primary keys
modelBuilder.Entity<Restaurant>().HasKey(q => q.Id);
modelBuilder.Entity<Specialties>().HasKey(q => q.Id);
// Relationship
modelBuilder.Entity<Restaurant>()
.HasMany(q => q.Specialties)
.WithMany(q => q.Restaurant)
.Map(q =>
{
q.ToTable("RestaurantSpecialty");
q.MapLeftKey("RestaurantId");
q.MapRightKey("SpecialtyId");
});
And in my Seed function i'm seeding data like this:
context.Specialties.AddOrUpdate(
p => p.Specialty,
new Specialties { Specialty = Specialty.Chinees, Name = "Chinees" },
new Specialties { Specialty = Specialty.Spanish, Name = "Spanish" },
);
ICollection<Specialties> lstSpecialties = new List<Specialties>();
lstSpecialties.Add(context.Specialties.FirstOrDefault(x => x.Name == "Chinees"));
context.Restaurants.AddOrUpdate(
p => p.Name,
new Restaurant{ Name = "Ctr test1", Specialties = lstSpecialties, Comments = "sfasd" },
new Restaurant{ Name = "Ctr test2", Specialties = lstSpecialties, Comments = "asd" },
);
But my table RestaurantSpecialties doesn't contain anything. I was expecting to see Id's of both tables.
What am I doing wrong?
[EDIT]
The enum has been renamed from 'Specialty' to 'RestaurantSpecialty' like Gert suggested to do (just the rename part, not the actual name).
The plural class 'Specialties' has also been renamed to 'Specialty'. Also because Gert suggested it.
The join table has also been removed from the model. The Fluent Api part in my original post has been updated accordingly.
Catering(s) has been renamed to Restaurant(s) which i forgot to do so in the beginning. So to avoind confusions i've done this too in my original code post.
context.Specialties.AddOrUpdate(
new Specialty { CateringSpecialty = RestaurantSpecialty.Chinese, Name = "Chinese" },
new Specialty { CateringSpecialty = CateringSpecialty.Greeks, Name = "Greeks" },
);
var aSpecialty = context.Specialties.FirstOrDefault(x => x.Name == "Chinese");
var restaurant1 = context.Restaurants.Include(c => c.Specialties).FirstOrDefault(x => x.Name == "Ctr test1");
if (restaurant1 == null)
{
restaurant1 = new Restaurant
{
Name = "Ctr test4",
Specialties = new List<Specialty> { aSpecialty },
Comments = "testtttttttt"
};
context.Restaurants.Add(restaurant1);
}
else
{
if (!restaurant1.Specialties.Any(s => s.Id == aSpecialty.Id))
restaurant1.Specialties.Add(aSpecialty);
restaurant1.Name = "Ctr test4";
restaurant1.Comments = "testtttt";
}
Try to add/update manually. I suspect that the AddOrUpdate method does not support updating a full object graph of related entities. Probably it supports adding related entities together with its parent entity if the parent entity doesn't exist yet. But if the "Chinees" specialty was already in the database without related entities the relationships possibly don't get updated.
A "manual update" would look like this:
context.Specialties.AddOrUpdate(
p => p.Specialty,
new Specialties { Specialty = Specialty.Chinees, Name = "Chinees" },
new Specialties { Specialty = Specialty.Spanish, Name = "Spanish" },
);
var chineesSpecialty = context.Specialties
.FirstOrDefault(x => x.Name == "Chinees");
var catering1 = context.Caterings.Include(c => c.Specialties)
.FirstOrDefault(x => x.Name == "Ctr test1");
if (catering1 == null)
{
catering1 = new Catering
{
Name = "Ctr test1",
Specialties = new List<Specialties> { chineesSpecialty },
Comments = "sfasd"
};
context.Caterings.Add(catering1);
}
else
{
if (!catering1.Specialties.Any(s => s.Id == chineesSpecialty.Id))
catering1.Specialties.Add(chineesSpecialty);
catering1.Comments = "sfasd";
}
// and the same for "Ctr test2"
This is also mentioned in this answer but it was during the early preview releases of Code-First Migrations, so I am not sure if this limitation still exists. But it would explain why you don't get entries in the relationship table.
Your mapping itself looks correct to me. (But as already recommended by Gert Arnold in the comments I would really remove the RestaurantSpecialties entity which looks redundant and only complicates your model.)
Related
I'm trying to perform a search on top of a dictionary using the Search method from RavenDB 4. Strangely, if the search term is the word in or it I get random results back. I'm absolutely sure that none of the records contains those words. It also happens when executing the equivalent lucene query on the studio. It works as expected when I enter a valid search term like the employee's name, number, etc.
I've managed to create this simple scenario based on the real one.
Here's the index:
public class Search : AbstractIndexCreationTask<Employee, Page>
{
public Search()
{
Map = employees => from employee in employees
select new
{
Id = employee.Id,
Details = employee.Details
};
Reduce = results => from result in results
group result by new
{
result.Id,
result.Details
}
into g
select new
{
g.Key.Id,
g.Key.Details
};
Index("Details", FieldIndexing.Search);
}
}
Employee class:
public class Employee
{
public string Id { get; set; }
public Dictionary<string, object> Details { get; set; }
}
Adding employees:
details = new Dictionary<string, object>();
details.Add("EmployeeNo", 25);
details.Add("FirstNames", "Yuri");
details.Add("Surname", "Cardoso");
details.Add("PositionCode", "XYZ");
details.Add("PositionTitle", "Developer");
employee = new Employee
{
Details = details
};
session.Store(employee);
session.SaveChanges();
Search method:
var searchTerm = "in";
var result = session
.Query<Page, Search>()
.Search(i => i.Details, $"EmployeeNo:({searchTerm})")
.Search(i => i.Details, $"FirstNames:({searchTerm})", options: SearchOptions.Or)
.Search(i => i.Details, $"Surname:({searchTerm})", options: SearchOptions.Or)
.Search(i => i.Details, $"PositionCode:({searchTerm})", options: SearchOptions.Or)
.Search(i => i.Details, $"PositionTitle:({searchTerm})", options: SearchOptions.Or)
.ToList();
Lucene query outputed:
from index 'Search' where search(Details, "EmployeeNo:(it)")
or search(Details, "FirstNames:(it)")
or search(Details, "Surname:(it)")
or search(Details, "PositionCode:(it)")
or search(Details, "PositionTitle:(it)")
Any idea why random results are returned when those specific words are enterered?
The issue is stop words. Certain terms are so common, that they are meaningless for searching using full text search.
is, it, they, are, etc.
They are erased by the query analyzer.
See the discussion here: https://ravendb.net/docs/article-page/4.2/Csharp/indexes/using-analyzers
You can use a whitespace analyzer, instead of the Standard Analyzer, since the former doesn't eliminate stop words.
After getting help from the RavenDB group guys, we've managed to find a solution for my scenario.
Employee:
public class Employee
{
public string Id { get; set; }
public string DepartmentId { get; set; }
public Dictionary<string, object> Details { get; set; }
}
Department:
public class Department
{
public string Id { get; set; }
public string Name { get; set; }
}
Page:
public class Page
{
public string Id { get; set; }
public string Department { get; set; }
public Dictionary<string, object> Details { get; set; }
}
Index (with dynamic fields):
public class Search : AbstractIndexCreationTask<Employee, Page>
{
public Search()
{
Map = employees => from employee in employees
let dept = LoadDocument<Department>(employee.DepartmentId)
select new
{
employee.Id,
Department = dept.Name,
_ = employee.Details.Select(x => CreateField(x.Key, x.Value))
};
Store(x => x.Department, FieldStorage.Yes);
Index(Constants.Documents.Indexing.Fields.AllFields, FieldIndexing.Search);
}
}
Query:
using (var session = DocumentStoreHolder.Store.OpenAsyncSession())
{
var searchTearm = "*yu* *dev*";
var result = await session
.Advanced
.AsyncDocumentQuery<Page, Search>()
.Search("Department", searchTearm)
.Search("EmployeeNo", searchTearm)
.Search("FirstNames", searchTearm)
.Search("Surname", searchTearm)
.Search("PositionCode", searchTearm)
.Search("PositionTitle", searchTearm)
.SelectFields<Page>()
.ToListAsync();
}
Everything seems to be working fine this way, no more random results.
Big thanks to Ayende and Egor.
I have this model `
public int Id { get; set; }
public string Title { get; set; }
public int CopiesNum { get; set; }
public int CurrentCopiesNum { get; set; )
public Author author { get; set; }
public int AuthorId { get; set; }
i want throug this action to show up the name of author which i made it to search `
public ActionResult SearchForBook(string BookName)
{
var book1 = from s in db.Books
select s;
if (!string.IsNullOrEmpty(BookName))
{
book1 = book1.Where(c => c.Title.Contains(BookName));
}
ViewBag.AuthorId = new SelectList(db.Authors, "AuthorId", "Name").ToList();
// var book = db.Books.Include(b => b.author).ToList();
return View(book1);
}
when i try to show the name of author
<td>
#Html.DisplayFor(modelItem => item.author.Name)
</td>
it shows nothing
As you are using Eager loading, you need to include Author. Try something like this:
In your Controller-
var book1 = db.Books.Include(a => a.Author).ToList();
If you want to use lazy loading then in the model
public virtual Author author { get; set; }
Then your controller should work.
Modification
As you said about your requirement in the comment section, this can also be achieved like below:
In you Controller:
var book1 = new Book();
if (!string.IsNullOrEmpty(BookName))
{
book1 = _context.Books.Include(c => c.Author).SingleOrDefault(c => c.Title == "BookName");
}
You can simply send book1 to the view (You should check if book1 contains anything and if not then handle it properly).
And if you really don't want to change the way you coded, you can edit the Controller like below:
var book1 = from s in _context.Books.Include(c => c.Author)
select s;
And keep the rest of the code as it is. It should also work (though I do not recommend this.)
I'm trying to use seed method like this;
context.Reeves.AddOrUpdate(
p => new { p.FirstName, p.LastName },
new Reeve { FirstName = "A", LastName = "A" },
new Reeve { FirstName = "B", LastName = "B" });
context.SaveChanges();
context.Districts.AddOrUpdate(
p => p.Name,
new District() { Name = "X", ReeveId = context.Reeves.First(r => r.FirstName == "A" && r.LastName == "A").Id },
new District() { Name = "Y", ReeveId = context.Reeves.First(r => r.FirstName == "B" && r.LastName == "B").Id });
context.SaveChanges();
I'm receiving error message that "The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.District_dbo.Reeve_Id". The conflict occurred in database "ProjectTracking", table "dbo.Reeve", column 'Id'."
If i change the code like below;
context.Districts.AddOrUpdate(
p => p.Name,
new District() { Name = "X", Reeve = context.Reeves.First(r => r.FirstName == "A" && r.LastName == "A") },
new District() { Name = "Y", Reeve = context.Reeves.First(r => r.FirstName == "B" && r.LastName == "B") });
context.SaveChanges();
Error message disapear but when i check the districts table i see all ReeveId columns are 0.
What is my mistake, any idea?
PS: I dont want to create inline Reeve's inside District's AddOrUpdate methods. Something like; context.Districts.AddOrUpdate(p => p.Name, new District() { Name = "X", Reeve = new Reeve () { FirstName = "A", LastName = "A" });
My Entities
public class Reeve
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName
{
get { return string.Format("{0} {1}", FirstName, LastName); }
}
public virtual District District { get; set; }
public byte[] RowVersion { get; set; }
}
public class District
{
public District()
{
Projects = new HashSet<Project>();
ProjectRequests = new HashSet<ProjectRequest>();
}
public int Id { get; set; }
public string Name { get; set; }
public int ReeveId { get; set; }
public virtual Reeve Reeve { get; set; }
public byte[] RowVersion { get; set; }
public virtual ICollection<Project> Projects { get; set; }
public virtual ICollection<ProjectRequest> ProjectRequests { get; set; }
}
Entity Configurations
public class ReeveConfiguration : EntityTypeConfiguration<Reeve>
{
public ReeveConfiguration()
{
HasKey<int>(p => p.Id);
Ignore(p => p.FullName);
Property(p => p.FirstName).HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("FullName", 1) { IsUnique = true })).HasMaxLength(50).IsRequired();
Property(p => p.LastName).HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("FullName", 2) { IsUnique = true })).HasMaxLength(50).IsRequired();
Property(p => p.RowVersion).IsRowVersion();
}
}
public class DistrictConfiguration : EntityTypeConfiguration<District>
{
public DistrictConfiguration()
{
HasKey<int>(p => p.Id);
HasRequired(p => p.Reeve).WithOptional(p => p.District);
HasMany(p => p.Projects).WithRequired(p => p.District).HasForeignKey(p => p.DistrictId);
HasMany(p => p.ProjectRequests).WithRequired(p => p.District).HasForeignKey(p => p.DistrictId);
Property(p => p.Name).HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute() { IsUnique = true })).HasMaxLength(100).IsRequired();
Property(p => p.RowVersion).IsRowVersion();
}
}
I hate entity framework team's 1 to 1 relationship rules. Delegated entity is must be same name PK with principle entity and also delegated entity PK must be also FK. This must be a joke. All 1 to 1 relationship can not be like Person -> PersonPhoto or Car -> Steering wheel. Am i right or i misunderstand their logic. For example i have also project and project request entities, project's request and request's project can be null i mean they have 0..1 to 0..1 relationship and they must be own PK Id's. Also how about that if i have Entity base class that have Id primary key field. How can i derived my 1 to 1 relation entities from that.
Ok, i solved my problem myself. Let's talk again my expectations. I want to write base class for my entities, at first step it's only contain Id property. Also i want to control my foreign key names, i don't want to ef do it for me automatically.
Also let's talk about received error too. I can not seed data and receive an error because of ef creating foreign key for me automatically and in district entity ReeveId column creating something like other data column. So when i set District.ReeveId with existing reeve and save changes ef throw foreign key error.
I do below changes to my code for solve the problem toward ef and my expectations;
Delete ReeveId from district entity because there is no need
Add HasRequired(p => p.Reeve).WithOptional(p => p.District).Map(m => m.MapKey("ReeveId")); code to my district configuration
So both table contain own Id column for primary key, district also have ReeveId column for foreign key. As a result ef and my expectations met.
Having a list of items I need to sort/order items within that list based on outter list's id
public class Venue
{
public int Id { get; set; }
public string Code { get; set; }
public IEnumerable<Machines> Machines { get; set; }
}
public class Machine
{
public string Name { get; set; }
}
Suppose I have a list of 3 venues and I want to sort only Venue's Machines where Venue.Id = 1;
Having a list of venues, I want to always sort by Venue Code, THEN sort machines by Name of Venue with Id = 1;
I tried this but doesn't work correctly:
query = query.OrderBy(x => x.Code).ThenBy(y => y.Machines.OrderBy(q => q.Name).Where(x => y.Id == 1))
If you want to sort a property of an object which is an IEnumerable<T> you need to select a new:
query = query
.Select(v => new Venue
{
Id = v.Id,
Code = v.Code,
Machines = v.Id == 1 ? v.Machines.OrderBy(m => m.Name) : v.Machines
})
.OrderBy(v => v.Code);
How should I Map an object with a nullable field? I guess I must turn the nullable field into a non-nullable version, and it's that step that I stumble upon.
What is the proper way to map nullable properties?
public class Visit {
public string Id { get; set; }
public int? MediaSourceId { get; set; }
}
public class MapReduceResult
{
public string VisitId { get; set; }
public int MediaSourceId { get; set; }
public string Version { get; set; }
public int Count { get; set; }
}
AddMap<Visit>(
visits =>
from visit in visits
select new
{
VisitId = visit.Id,
MediaSourceId =
(visit.MediaSourceId.HasValue)
? visit.MediaSourceId
: UNUSED_MEDIASOURCE_ID,
Version = (string) null,
Count = 1
});
This doesn't work! In fact; this Map is completely ignored, while the other Maps work fine, and they are in the end Reduced as expected.
Thanks for helping me!
Below is a newly added test case that fails with a "Cannot assign <null> to anonymous type property". How am I supposed to get this flying with the least amount of pain?
[TestFixture]
public class MyIndexTest
{
private IDocumentStore _documentStore;
[SetUp]
public void SetUp()
{
_documentStore = new EmbeddableDocumentStore {RunInMemory = true}.Initialize();
_documentStore.DatabaseCommands.DisableAllCaching();
IndexCreation.CreateIndexes(typeof (MyIndex).Assembly, _documentStore);
}
[TearDown]
public void TearDown()
{
_documentStore.Dispose();
}
[Test]
public void ShouldWork()
{
InitData();
IList<MyIndex.MapReduceResult> mapReduceResults = null;
using (var session = _documentStore.OpenSession())
{
mapReduceResults =
session.Query<MyIndex.MapReduceResult>(
MyIndex.INDEX_NAME)
.Customize(x => x.WaitForNonStaleResults()).ToArray();
}
Assert.That(mapReduceResults.Count, Is.EqualTo(1));
}
private void InitData()
{
var visitOne = new Visit
{
Id = "visits/64",
MetaData = new MetaData {CreatedDate = new DateTime(1975, 8, 6, 0, 14, 0)},
MediaSourceId = 1,
};
var visitPageVersionOne = new VisitPageVersion
{
Id = "VisitPageVersions/123",
MetaData = new MetaData {CreatedDate = new DateTime(1975, 8, 6, 0, 14, 0)},
VisitId = "visits/64",
Version = "1"
};
using (var session = _documentStore.OpenSession())
{
session.Store(visitOne);
session.Store(visitPageVersionOne);
session.SaveChanges();
}
}
public class MyIndex :
AbstractMultiMapIndexCreationTask
<MyIndex.MapReduceResult>
{
public const string INDEX_NAME = "MyIndex";
public override string IndexName
{
get { return INDEX_NAME; }
}
public class MapReduceResult
{
public string VisitId { get; set; }
public int? MediaSourceId { get; set; }
public string Version { get; set; }
public int Count { get; set; }
}
public MyIndex()
{
AddMap<Visit>(
visits =>
from visit in visits
select new
{
VisitId = visit.Id,
MediaSourceId = (int?) visit.MediaSourceId,
Version = (string) null,
Count = 1
});
AddMap<VisitPageVersion>(
visitPageVersions =>
from visitPageVersion in visitPageVersions
select new
{
VisitId = visitPageVersion.VisitId,
MediaSourceId = (int?) null,
Version = visitPageVersion.Version,
Count = 0
});
Reduce =
results =>
from result in results
group result by result.VisitId
into g
select
new
{
VisitId = g.Key,
MediaSourceId = (int?) g.Select(x => x.MediaSourceId).FirstOrDefault(),
Version = g.Select(x => x.Version).FirstOrDefault(),
Count = g.Sum(x => x.Count)
};
}
}
}
You don't need to do anything to give nullables special treatment.
RavenDB will already take care of that.