Building objects with many-to-many relationship using Dapper - c#

Consider an Sqlite database, whose partial schema is shown below (we are not considering the Book_Tag table here). Note the many-to-many relationship between media items and tags using the link table Media_Tag:
An object model for these tables is as follows:
public enum MediaType
{
Dvd,
BluRay,
Cd,
Vhs,
Vinyl,
Other
}
public class MediaItem
{
public MediaType type { get; set; }
public long number { get; set; }
public int runningTime { get; set; }
public int releaseYear { get; set; }
public ICollection<Tag> tags { get; set; }
}
public class Tag
{
public string name { get; set; }
}
currently, Dapper is being used to read from the Media table, but without considering tags. The code is as follows:
public IEnumerable<MediaItem> readAll()
{
using (var db = new SqliteConnection(this.connectionString))
{
db.Open();
var sql = "SELECT * FROM Media;";
return db.Query<MediaItem>(sql);
}
}
public MediaItem readById(int id)
{
using (var db = new SqliteConnection(this.connectionString))
{
db.Open();
var sql = "SELECT * FROM Media WHERE id = #id;";
var #params = new { id = id };
return db.Query<MediaItem>(sql, #params).First();
}
}
How to change this so that the tag property of MediaItem is considered when creating the objects, for both cases (read by id and read all rows from the table)? Is a join query required? I'm sure Dapper has a way of doing this nicely, but I don't know how it's done.

You are not interested in anything from the link table so something like this SQL should do:
SELECT M.Id, M.title, M.type, M.Number, M.image, M.runningTime, M.releaseYear, T.Id, T.Name FROM Media as M
INNER JOIN Media_Tag AS MT ON M.id = MT.mediaId
INNER JOIN Tags AS T ON T.id = MT.tagId
If SqLite allows you can use M.*, T.* instead.
I have taken the liberty to add Id properties to your entity classes. I think you are going to need it, otherwise all your tags will be different instead of being unique. You might make it work without it, but it should make your life easier.
public class MediaItem
{
public int Id { get; set; } // New
public MediaType type { get; set; }
public long number { get; set; }
public int runningTime { get; set; }
public int releaseYear { get; set; }
public ICollection<Tag> tags { get; set; }
}
public class Tag
{
public int Id { get; set; } // New
public string name { get; set; }
}
Since both your entity classes have a unique id, you will have to pick them up and make sure they are unique going through the results. We do that by using dictionaries to keep them. I'm only showing the ReadAll, you should be able to do ReadById accordingly.
string sql = "<see above>";
using (var db = new SqliteConnection(this.connectionString))
{
var mediaDictionary = new Dictionary<int, Media>();
var tagDictionary = new Dictionary<int, Tag>();
var list = db.Query<Media, Tag, Media>(
sql,
(media, tag) =>
{
Media mediaEntry;
if (!mediaDictionary.TryGetValue(media.Id, out mediaEntry))
{
// Haven't seen that one before, let's add it to the dictionary
mediaEntry = media;
mediaDictionary.Add(mediaEntry.Id, mediaEntry);
}
Tag tagEntry;
if (!tagDictionary.TryGetValue(tag.Id, out tagEntry))
{
// Haven't seen that one before, let's add it to the dictionary
tagEntry = tag;
tagDictionary.Add(tagEntry.Id, tagEntry);
}
// Add the tag to the collection
mediaEntry.Tags.Add(tagEntry);
return mediaEntry;
},
splitOn: "Id") // This default and could be omitted
.Distinct()
.ToList();

Related

How to hide items, in an API reply, from a db query?

I'm currently using MVC with EF to have a small server with API querying a SQL database. But in the API reply I'm not able to hide some parameters.
The main object
public class AssetItem
{
[Key]
public Int32 AssetId { get; set; }
public String AssetName { get; set; }
public int OdForeignKey { get; set; }
[ForeignKey("OdForeignKey")]
public OperationalDataItem OperationalDataItem { get; set; }
}
The other one:
public class OperationalDataItem
{
[Key]
public Int32 OperationalDataId { get; set; }
public String Comunity { get; set; }
public List<AssetItem> AssetItems { get; set; }
}
From what I have read, this should be ok, I have also set the context:
public AssetContext(DbContextOptions<AssetContext> options) : base(options)
{}
public DbSet<AssetItem> AssetItems { get; set; }
public DbSet<OperationalDataItem> OperationalDataItems { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AssetItem>().HasOne(p =>
p.OperationalDataItem).WithMany(b => b.AssetItems).HasForeignKey(p =>
p.OdForeignKey);
}
And the seeding in program.cs
context.AssetItems.Add(
new AssetItem { AssetName = "Test test", OdForeignKey = 1,
OperationalDataItem =
new OperationalDataItem {Comunity = "Comunity1" }});
So calling the API this results in:
{ "assetId":3,
"assetName":"Test test",
"odForeignKey":1,
"operationalDataItem":null }
From what I read this is because of the lazy loading, how can I hide the result operationalDataItem?
In case is not possible i have of course try to query for it and give it back and it give something like:
{ "assetId":3,
"assetName":"Test test",
"odForeignKey":1,
"operationalDataItem":
{ "operationalDataId":1,
"comunity":"Comunity1",
"assetItems":[
But in this case I would like to hide "assetsItems" in the reply to the FE.
How can I hide those parameters?
The API is quite simple, just an example code:
var todoItem = await _context.AssetItems.FindAsync((Int32)id);
var item = _context.OperationalDataItems.Find((Int32)todoItem.OdForeignKey);
todoItem.OperationalDataItem = item;
return todoItem
If you want to fetch data from the database, but you only want to fetch some properties, use Select. Usually this is more efficient than using Find, because you'll only transfer the data that you actually plan to use.
To fetch some properties of the assetItem that has primary key assetItemId:
var result = dbContext.AssetItems
.Where(assetItem => assetItem.AssetItmId = assetItemId)
.Select(assetItem => new
{
// Select only the properties that you plan to use
Id = assetItem.AssertItemId,
Name = assetItem.Name,
OperationalData = new
{
// again, select only the properties that you plan to use
Id = assetItem.OperationalData.OperationalDataId,
Community = assetItem.OperationalData.Community,
},
})
.FirstOrDefault();
Or the other way round:
Fetch several properties of all (or some) OperationalDataItems, each with some properties of all (or some) of its AssetItems:
var result = dbContext.OperqationalDataItems
.Where(operationalDataItem => ...) // only if you don't want all
.Select(operationalDataItem => new
{
Id = operationalDataItem.Id,
Community = operationalDataItem.Community
AssetItems = operationalDataItem.AssetItems
.Where(assetItem => ...) // only if you don't want all its assetItems
.Select(assetItem => new
{
// Select only the properties you plan to use:
Id = assetItem.Id,
...
// not useful: you know the value of the foreign key:
// OperationalDataId = assetItem.OperationalDataId,
})
.ToList();
})
.ToList(); // or: FirstOrDefault if you expect only one element
Entity framework knows your one-to-many relation and is smart enough to know which (group-)join is needed for your query.
Some side remarks
You've declare your many-relation a List<AssetItem>. Are you sure that operationalDataItem.AssetItems[4] has a defined meaning? Wouldn't it be better to stick to the entity framework code first conventions? This would also eliminate the need for most attributes and / or fluent API
public class OperationalDataItem
{
public int Id { get; set; }
public String Comunity { get; set; }
...
// Every OperationalDataItem has zero or more AssetItems (one-to-many)
public virtual ICollection<AssetItem> AssetItems { get; set; }
}
public class AssetItem
{
public int Id { get; set; }
public String Name { get; set; }
...
// every AssetItem belongs to exactly one OperationalDataItem, using foreign key
public int OperationDataItemId { get; set; }
public virtual OperationalDataItem OperationalDataItem { get; set; }
}
In entity framework the columns of a table are represented by the non-virtual properties. The virtual properties represent the relations between the tables (one-to-many, many-to-many)
Because I stuck to the conventions, no attributes nor fluent API is needed. Entity framework is able to detect the one-to-many relation and the primary and foreign keys. Only if I am not satisfied with the names or the types of the columns I would need fluent API.

Entity Framework inserting new rows instead of updating them

I have a problem when I am updating data to database. When I want to update data, Entitiy Framework adds new rows to tables that can have multiple rows (tables that have foreign key).
Database model:
When I update Phone/Contact or Tags entity, Entity Framework automatically adds new row instead of updating it
Here is code that I used:
public string UpdateContact(Contact contact)
{
if (contact != null)
{
int id = Convert.ToInt32(contact.id);
Contact Updatecontact = db.Contacts.Where(a => a.id == id).FirstOrDefault();
Updatecontact.firstname = contact.firstname;
Updatecontact.lastname = contact.lastname;
Updatecontact.address = contact.address;
Updatecontact.bookmarked = contact.bookmarked;
Updatecontact.city = contact.city;
Updatecontact.notes = contact.notes;
Updatecontact.Emails1 = contact.Emails1;
Updatecontact.Phones1 = contact.Phones1;
Updatecontact.Tags1 = contact.Tags1;
db.SaveChanges();
return "Contact Updated";
}
else
{
return "Invalid Record";
}
}
EDIT:
Here is EF Model code:
Contact:
public partial class Contact
{
public Contact()
{
this.Emails1 = new HashSet<Email>();
this.Phones1 = new HashSet<Phone>();
this.Tags1 = new HashSet<Tag>();
}
public int id { get; set; }
public string firstname { get; set; }
public string lastname { get; set; }
public string address { get; set; }
public string city { get; set; }
public Nullable<byte> bookmarked { get; set; }
public string notes { get; set; }
public virtual ICollection<Email> Emails1 { get; set; }
public virtual ICollection<Phone> Phones1 { get; set; }
public virtual ICollection<Tag> Tags1 { get; set; }
}
Emails/Tags and Phone have same model (with different name for value)
public partial class Email
{
public int id { get; set; }
public int id_contact { get; set; }
public string email1 { get; set; }
public virtual Contact Contact1 { get; set; }
}
Update properties rather than set new objects.
Updatecontact.Emails1.email1 = contact.Emails1.email1;
Updatecontact.Phones1.number = contact.Phones1.number;
Updatecontact.Tags1.tag1 = contact.Tags1.tag1;
Edit: seems that your contact model has lists of emails, phones and tags. If this is so, then simple assignment won't work. Instead, when sent from the client, you have to find one-by-one and update:
foreach ( var email in contact.Emails1 )
{
// first make sure the object is retrieved from the database
var updateemail = Updatecontact.Emails1.FirstOrDefault( e => e.id == email.id );
// then update its properties
updateemail.email1 = email.email1;
}
// do the same for phones and tags
It's doing that because you're setting the different HashSet values to the values of a completely different collection, namely from what you call contact in that method. In order for you to properly do an update, you're going to have to loop through the emails, phones, and tags to check if those need to be added/updated/deleted on the actual object that you're trying to update.
First, why do you have to search for the contact if you are already receiving it by parameter? That makes me think that you are creating a new one because you are in a different context, if so, then it creates a new record because you have 2 different object in 2 different context.
Try using just one object in the same context to update, EF should mark the object to modification by itself, if not then try making sure before saving that your object has EntityState.Modified.

Violation of PRIMARY KEY constraint: Cannot insert duplicate key in object

When I want to persist a complex model, I get this error. I think I know where it comes from, but I don't know how to solve it. I'm importing a few feeds and create objects automatically, including children (many-to-many).
{"Violation of PRIMARY KEY constraint 'PK_dbo.Parent'. Cannot insert
duplicate key in object 'dbo.Parent'. The duplicate key value is
(291).\r\nThe statement has been terminated."}
The error speaks for itself, but how to prevent it? :)
The code that triggers it
var parser = new SchoolFeedReader();
var update = parser.GetAll();
var students = Mapper.Map<List<StudentDTO>, List<Student>>(update);
using (var db = new SchoolContext())
{
// I'm updating every night, so clean out the database before import
db.Database.ExecuteSqlCommand("DELETE FROM Student");
db.Database.ExecuteSqlCommand("DELETE FROM Parent");
db.Database.ExecuteSqlCommand("DELETE FROM Subject");
db.Database.ExecuteSqlCommand("DELETE FROM StudentParent");
db.Database.ExecuteSqlCommand("DELETE FROM StudentSubject");
students.ForEach(s => db.Students.Add(s));
db.SaveChanges(); // Triggers the Exception
}
The TL;DR
For a schoolproject I need to import 3 XML Feeds into the database.
Students.xml
Parents.xml
Subjects.xml
In Students.xml I encountered a design flaw: a fixed number (3) of possible Parents.
<student>
<StudentId>100</StudentId>
<Name>John Doe</Name>
<Location>Main Street</Location>
<Parent1>1002</Parent1>
<Parent2>1002</Parent2>
<Parent3/>
</student>
(... more students)
In Parents.xml, things are more straightforward.
<parent>
<ParentId>1102</ParentId>
<Name>Dad Doe</Name>
<Email>dad#doe.com</Email>
</parent>
(... more parents)
And Subjects.xml is also very simple.
<subject>
<StudentId>100</StudentId>
<Name>English</Name>
</subject>
(... more subjects)
The Models
So I created 3 models, including the DTOs.
public class Student
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public long StudentId { get; set; }
public string Name { get; set; }
public string Location { get; set; }
[InverseProperty("Students")]
public virtual ICollection<Parent> Parents { get; set; }
public virtual ICollection<Subject> Subjects { get; set; }
}
public class StudentDTO
{
public long StudentId { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public List<ParentDTO> Parents { get; set; }
public List<SubjectDTO> Subjects { get; set; }
}
public class Parent
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public long ParentId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
[InverseProperty("Parents")]
public virtual ICollection<Student> Students { get; set; }
}
public class ParentDTO
{
public long ParentId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public List<StudentDTO> Students { get; set; }
public ParentDTO()
{
Students = new List<StudentDTO>();
}
}
public class Subject
{
public long SubjectId { get; set; }
public string Name { get; set; }
public virtual List<Student> Students { get; set; }
}
public class SubjectDTO
{
public string Name { get; set; }
public List<StudentDTO> Students { get; set; }
public SubjectDTO()
{
Students = new List<StudentDTO>();
}
}
From XML to DTOs
The Importer class has this giant LINQ query to get everything I need in one big swoop.
var query = from student in _xStudents.Descendants("Student")
select new StudentDTO
{
StudentId = (long)student.Element("StudentId"),
Name = (String)student.Element("Name"),
Subjects = (
from subject in _xSubjects.Descendants("Subject").DefaultIfEmpty()
where (String)student.Element("StudentId") == (String)subject.Element("StudentId")
select new SubjectDTO
{
Name = (String)subject.Element("Name")
}
).ToList(),
Parents = (
from parent in _xParents.Descendants("Parent").DefaultIfEmpty()
group parent by (String)parent.Element("ParentId") into pg
where (String)student.Element("Parent1") == (String)pg.FirstOrDefault().Element("ParentId") ||
(String)student.Element("Parent2") == (String)pg.FirstOrDefault().Element("ParentId") ||
(String)student.Element("Parent3") == (String)pg.FirstOrDefault().Element("ParentId")
select new ParentDTO
{
ParentId = (long)pg.FirstOrDefault().Element("ParentId"),
Name = (String)pg.FirstOrDefault().Element("Name")
}
).ToList()
};
That works fine, some students get 2 parents, some get 1, so my data looks good.
The Problem
I have these AutoMappers in my Global.asax.cs:
Mapper.CreateMap<StudentDTO, Student>()
.ForMember(dto => dto.Parents, opt => opt.MapFrom(x => x.Parents))
.ForMember(dto => dto.Subjects, opt => opt.MapFrom(x => x.Subjects));
Mapper.CreateMap<ParentDTO, Parent>();
Mapper.CreateMap<SubjectDTO, Subject>();
But when I start the import I get errors on my db.SaveChanges(). It complains about a duplicate ForeignKey on the Parent model. So I'm thinking:
it's a Many-to-Many relationship, so if John Doe's sister, Jane Doe, tries to insert the same Dad Doe, then it crashes
So How can I make sure that the entire set of Mapped Business Objects only have 1 reference to each entity; how to delete the duplicate daddy's and mommy's? I probably want to do this also for Subject.
If two or more student in _xStudents.Descendants("Student") reference the same parent (by id), you then create two or more ParentDTOs with the same id, so you are then trying to insert the same Primary Key twice within your Importer class.
If you simply pre-process your _xParents, to transform them into a new list of ParentDTO, which is unique by ParentId you can then use that in your var query to get a reference to the single ParentDTO instance that refers to the given ParentId PK.
This code sample doesn't change your code much so that you can easily relate it to your original. Note, however, that you can probably optimise this, and you will also have the same problem with your SubjectDTO list as well if you are using SubjectDTO.Name to be unique (as you should be, I guess).
var parents = (from parent in _xParents.Descendants("Parent").DefaultIfEmpty()
group parent by (String)parent.Element("ParentId") into pg
select new ParentDTO
{
ParentId = (long)pg.FirstOrDefault().Element("ParentId"),
Name = (String)pg.FirstOrDefault().Element("Name")
// you might want to not use ToList here and let parents be an IEnumerable instead
}).ToList();
var query = from student in _xStudents.Descendants("Student")
select new StudentDTO
{
StudentId = (long)student.Element("StudentId"),
Name = (String)student.Element("Name"),
Subjects = (
from subject in _xSubjects.Descendants("Subject").DefaultIfEmpty()
where (String)student.Element("StudentId") == (String)subject.Element("StudentId")
select new SubjectDTO
{
Name = (String)subject.Element("Name")
}
).ToList(),
Parents = (
from parent in parents
// Calling ToString each time is not fantastic
where (String)student.Element("Parent1") == parent.ParentId.ToString() ||
(String)student.Element("Parent2") == parent.ParentId.ToString() ||
(String)student.Element("Parent3") == parent.ParentId.ToString()
select parent
).ToList()
};
The real problem is in mapping. Mapper adds that same parent two times, and hence its new entity, it is in Added state. Later dbContext treats it like new record, and tries insert.
I see three options:
Replace StudentDTO.ParentDTO with StudentDTO.IDParentDTO
Add StudentDTO.IDParentDTO and ignore StudentDTO.ParentDTO in mapping
Play with mapping. There is a bunch of features but you just need to find them. Check this question
I received this error with a User-Defined Table Type. When building data relationships, I sometimes pull the same record multiple times. If appropriate, turn ON IGNORE_DUP_KEY when declaring your PRIMARY KEY.
Microsoft index_option (w / IGNORE_DUP_KEY)
Example:
CREATE TYPE [dbo].[udt_Promotion] AS TABLE(
[PromotionID] [int] NOT NULL PRIMARY KEY CLUSTERED WITH (IGNORE_DUP_KEY = ON),
...
)

There has to be a better way to add these using LINQ, right?

I am new to LINQ and and come up with the below to add new information to my DB using LINQ and EF5 but I am sure there is a more efficant, better, way to do this I just don't know it. I was hoping to get some input on what I can do to acceive the same but with less/more efficant code.
using (var db = new FullContext())
{
if (ddlItemType.SelectedValue == "Other")
{
var NewItemType = new ItemType { Name = tbNewType.Text };
db.ItemTypes.Add(NewItemType);
db.SaveChanges();
}
if (ddlRegion.SelectedValue == "Other")
{
var NewRegion = new ReleaseRegion { Name = tbNewRegion.Text };
db.Regions.Add(NewRegion);
db.SaveChanges();
}
var NewItemTypeID = byte.Parse((from i in db.ItemTypes
where i.Name == tbNewType.Text
select new { i.ID }).ToString());
var NewRegionID = byte.Parse((from r in db.Regions
where r.Name == tbNewRegion.Text
select new { r.ID }).ToString());
var NewItem = new Item
{
Name = tbItemName.Text,
TypeID = NewItemTypeID,
RegionID = NewRegionID,
Condition = ddlCondition.SelectedValue.ToString(),
UPC = tbUPC.Text,
ISBN = tbISBN.Text,
IsColleciton = cbIsCollection.Checked,
CollectionID = Convert.ToInt16(ddlCollection.SelectedValue),
Notes = tbNotes.Text
};
db.Items.Add(NewItem);
db.SaveChanges();
}
Item.cs:
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace FFCollection.DAL
{
[Table("Items")]
public class Item
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Int16 ID { get; set; }
[Required]
public string Name { get; set; }
public byte TypeID { get; set; }
[ForeignKey("TypeID")]
public virtual ItemType Type { get; set; }
public byte RegionID { get; set; }
[ForeignKey("RegionID")]
public virtual ReleaseRegion Region { get; set; }
[Required]
public string Condition { get; set; }
public string UPC { get; set; }
public string ISBN { get; set; }
public string Notes { get; set; }
[Required]
public Boolean IsColleciton { get; set; }
public Int16 CollectionID { get; set; }
[ForeignKey("CollectionID")]
public virtual Item InCollectionID { get; set; }
}
}
ItemType.cs:
using System.ComponentModel.DataAnnotations.Schema;
namespace FFCollection.DAL
{
public class ItemType
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public byte ID { get; set; }
public string Name { get; set; }
}
}
The databinding to DDL:
using (var db = new FullContext())
{
ddlItemType.DataSource = (from t in db.ItemTypes
select new { t.ID, t.Name }).ToList();
ddlItemType.DataTextField = "Name";
ddlItemType.DataValueField = "ID";
ddlItemType.DataBind();
ddlItemType.Items.Insert(0, new ListItem("Other", "Other"));
}
Part of the trouble isn't Linq, it's how you're using EF. Based on that example code you're using it as a data layer wrapper rather than an ORM. When constructing an object graph you should deal with the objects where you can, not foreign key IDs. The power of an ORM is that you can deal specifically with object graphs that are mapped to data, so that when you tell the ORM to save an object (and it's associated relatives) the ORM takes out all of the work of inserting/updating new records and wiring up keys. You're doing all that extra work in code, where an ORM like EF should allow you to accomplish what you want with a handful of lines.
For a start, when dealing with combo boxes, bind them to a data structure that includes the lookup value's ID that you can resolve instances of existing ItemTypes or Regions to associate with your new Item. (or in the case of selections of "other".
What I'd be looking at would be to bind the combo boxes to ItemType/Regions with the "Other" being a specific place-holder that the code will substitute with a new object if selected based on entries in the text fields. Then rather than saving the new objects before appending to the "Item", you simply set the references and save the Item which should cascade insert operations for the new lookup objects.
After this code executes EF will automatically put an ID into your NewItemType entity. You don't need to go and find it again, you could just say NewItemType.ID. This will only work after you have already called db.SaveChanges().
if (ddlItemType.SelectedValue == "Other")
{
var NewItemType = new ItemType { Name = tbNewType.Text };
db.ItemTypes.Add(NewItemType);
db.SaveChanges();
}

entity framework when many to many is holding data

I'm using Entity Framework CTP5.
I have a schema like this:
A group contains many textdescriptions.
A textdescriptions has many texts.
A Language has many texts.
So there are 4 tables.
Groups one-to-many DEscriptions many-to-many Texts many-to-one Languages.
So I have a many-to-many relationship where the relation also holds data.
Definitions of Text and TextDescription ( since we can query on the Id for Group and Languages I havent added them here )
public class Text
{
public int TextID { get; set; }
public int TextDescriptionID { get; set; }
public int LanguageID { get; set; }
public string OriginalText { get; set; }
public bool IsValid { get; set; }
public DateTime Added { get; set; }
public DateTime Updated { get; set; }
public Language Language { get; set; }
public TextDescription TextDescription { get; set; }
public static Text GetMissingText(string input)
{
Text text = new Text();
text.OriginalText = "Missing: " + input;
text.IsValid = true;
text.TextDescription = new TextDescription()
{
IsStatic = true,
Name = input,
IsMultiline = false,
};
return text;
}
}
public class TextDescription
{
public int TextDescriptionId { get; set; }
public int TextDescriptionGroupId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool UseHtml { get; set; }
public bool IsMultiline { get; set; }
public bool IsStatic { get; set; }
public TextDescriptionGroup TextDescriptionGroup { get; set; }
public virtual ICollection<Text> Texts { get; set; }
public static TextDescription GetNewItem(int textDescriptionGroupId)
{
var item = new TextDescription();
item.Name = item.Description = "n/a";
item.UseHtml = item.IsMultiline = item.IsMultiline = false;
item.TextDescriptionGroupId = textDescriptionGroupId;
return item;
}
}
When adding either a new language or a new text is inserted ... the many to many relation is not inserted into the database. (Think it would be a bad idea, so in the end, if thats the only solution, I could be able to that)
So how do I handle this in a smart way when I need to fetch all the text for a specific group from the database, but also get the translation if there are one for that languages.
I can't start fra the translation object, since its possible its not there. If I start to query from the Text entity ... how do I only select one language without getting all languages first.
repo.Find(x =>
x.GroupId == groupId &&
x.Translation.Any(a => a.LanguageID == id.Value)
);
I'm lost here ... any there any smart way ... so I wont have to query the database for all the Texts ... and then a query for each item ... to see if there are a translation? or else just make a new empty one.
In SQL I would do it like this:
SELECT TD.Name, T.OriginalText FROM TextDescriptions TD
LEFT JOIN Texts T ON TD.TextDescriptionId = T.TextDescriptionId
WHERE TextDescriptionGroupId = 41 AND ISNULL(T.LanguageId, 1) = 1
The above SQL will give me the elements even if there is not record now, I get a NULL for these values. I could then handle that it my code and avoid lazy load.
But can I get the same behavior in Entity Framework. I could see there would be some problems maybe for EF4 to do the mapping ... since I'm going from TextDesciptions to Texts ... and TextDesciptions have a List of Texts ... but here ... I only want either 1 or NULL, or just a new Entity that havent been added to the database yet.
Looking forward to some interesting answers.
mvh
For now ... if no other solution is found I will be running the follow SQL script to insert empty records. This way I'm sure the record is there when a user wants to edit it and dont have to ensure its there before saving it. Maybe also avoiding some naste Linq query.
I only have to run this SQL 2 places. When adding a new Language or new a new TextDesciption.
INSERT INTO Texts
SELECT TD.TextDescriptionId, L.LanguageId, '', 0, GETDATE(), GETDATE(), L.TwoLetterISOLanguageName
FROM TextDescriptions TD
INNER JOIN Languages L ON 1 = 1
LEFT JOIN Texts T ON
T.TextDescriptionId = TD.TextDescriptionId AND
T.LanguageId = L.LanguageId
WHERE TextId IS NULL

Categories

Resources