Sto usando un Microsoft.EntityFrameworkCore.DbQuery
per restituire POCO dal risultato di una funzione con valori di tabella nel mio database. Ho notato che nessuno dei miei POCO ha nessuno dei loro set di proprietà - sono tutti valori predefiniti - purché i loro modificatori di accesso siano diversi da quelli public
. Nel mio caso, i POCO stanno implementando tutte le interfacce in modo esplicito in modo che il consumatore non sappia (o non debba) sapere nulla sulle definizioni POCO, quindi voglio impostarle su internal
.
Perché i POCO devono essere pubblici? O c'è un modo per farlo in modo che non debbano essere pubblici? Ecco alcuni esempi di codice per dimostrarlo:
CoreLibrary.dll (.NET Standard 2.0)
using System.Collections.Generic;
namespace CoreLibrary
{
public interface IWidgetRepository
{
IReadOnlyCollection<IWidget> FindAllWidgets();
IReadOnlyCollection<IWidget> FindWidgets(string withText);
}
public interface IWidget
{
int ID { get; }
string Name { get; }
string Value { get; }
}
}
DataAccess.dll (.NET Standard 2.0)
using CoreLibrary;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
namespace DataAccess
{
public sealed class WidgetRepositoryApi : IWidgetRepository
{
private DatabaseContext _dbContext;
public WidgetRepositoryApi(string databaseServerName, string databaseName)
=> _dbContext = new DatabaseContext(databaseServerName, databaseName);
public IReadOnlyCollection<IWidget> FindAllWidgets()
=> _dbContext.Widget.ToList();
public IReadOnlyCollection<IWidget> FindWidgets(string withText)
{
string sqlQuery = "SELECT * FROM dbo.fn_SearchWidgetText(@Text)";
var ret = _dbContext.GetWidgetFullTextSearchMatches
.FromSql(sqlQuery, new SqlParameter("@Text",
System.Data.SqlDbType.NVarChar) { Value = withText });
return ret.ToList().AsReadOnly();
}
}
internal partial class DatabaseContext : DbContext
{
private string _dbConnectionString;
internal DatabaseContext(string databaseServerName, string databaseName)
=> _dbConnectionString =
$"Server={databaseServerName};Database={databaseName};Trusted_Connection=True;";
internal virtual DbSet<DBSetWidget> Widget { get; set; }
internal virtual DbQuery<DBQueryWidget> GetWidgetFullTextSearchMatches { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer(_dbConnectionString);
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<DBSetWidget>(entity =>
{
entity.Property(e => e.Id).HasColumnName("ID");
entity.Property(e => e.Name)
.IsRequired()
.HasMaxLength(50);
entity.Property(e => e.Value).IsRequired();
});
}
}
/********************************************************************
* NOTE: there need to be two widget implementations (POCOs)
* because otherwise the code throws
* System.InvalidCastException: 'Unable to cast object of type
* 'Microsoft.EntityFrameworkCore.Internal.
* InternalDbQuery`1[DataAccess.DBSetWidget]' to type
* 'Microsoft.EntityFrameworkCore.DbSet`1[DataAccess.DBSetWidget]'.'
*******************************************************************/
internal partial class DBQueryWidget : IWidget
{
internal int Id { get; set; }
internal string Name { get; set; }
internal string Value { get; set; }
int IWidget.ID => Id;
string IWidget.Name => Name;
string IWidget.Value => Value;
}
internal partial class DBSetWidget : IWidget
{
internal int Id { get; set; }
internal string Name { get; set; }
internal string Value { get; set; }
int IWidget.ID => Id;
string IWidget.Name => Name;
string IWidget.Value => Value;
}
}
ConsumerConsoleApp.exe (.NET Core 2.1)
using DataAccess;
namespace ConsumerConsoleApp
{
class Program
{
static void Main(string[] args)
{
var repo = new WidgetRepositoryApi(args[0], args[1]);
var allWidgets = repo.FindAllWidgets();
var someWidgets = repo.FindWidgets("facebook");
}
}
}
Tabella dei widget nel database SQL
CREATE TABLE [dbo].[Widget](
[ID] [INT] IDENTITY(1,1) NOT NULL,
[Name] [NVARCHAR](50) NOT NULL,
[Value] [NVARCHAR](MAX) NOT NULL,
CONSTRAINT [PK_Widget] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
fn_SearchWidgetText nel database
CREATE FUNCTION [dbo].[fn_SearchWidgetText]
(
@Text NVARCHAR(1000)
)
RETURNS TABLE
AS
RETURN
(
SELECT *
FROM dbo.Widget
WHERE CONTAINS(Value, @Text)
)
Se cambio tutti i miei modificatori di accesso nel mio spazio DataAccess
nomi DataAccess
in public
, ora i POCO vengono riempiti correttamente:
La risposta breve è solo perché è di progettazione.
L'osservazione è uno stato di qualsiasi oggetto può essere diviso in due parti. La prima parte è public state, e la seconda parte è lo stato interno dell'oggetto. Quando progetti la tecnologia dei data store, dovresti decidere cosa vuoi archiviare. Solo una parte o entrambi? EF è progettato per memorizzare quasi tutti gli oggetti (che possono avere una struttura interna complicata) in un database relativamente semplice. Quindi, è una buona ipotesi ignorare la struttura interna degli oggetti.