Подтвердить что ты не робот

LINQ to SQL - отображение исключения при использовании абстрактных базовых классов

Проблема: я хотел бы поделиться кодом между несколькими сборками. Этот общий код должен работать с LINQ to SQL-сопоставленными классами.

Я столкнулся с той же проблемой, что и здесь, , но я также нашел обход, который я нахожу тревожным (я не зашел так далеко сказать "ошибка" ).

Весь следующий код можно загрузить в это решение.

С учетом этой таблицы:

create table Users
(
      Id int identity(1,1) not null constraint PK_Users primary key
    , Name nvarchar(40) not null
    , Email nvarchar(100) not null
)

и это отображение DBML:

<Table Name="dbo.Users" Member="Users">
  <Type Name="User">
    <Column Name="Id" Modifier="Override" Type="System.Int32" DbType="Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true" CanBeNull="false" />
    <Column Name="Name" Modifier="Override" Type="System.String" DbType="NVarChar(40) NOT NULL" CanBeNull="false" />
    <Column Name="Email" Modifier="Override" Type="System.String" DbType="NVarChar(100) NOT NULL" CanBeNull="false" />
  </Type>
</Table>

Я создал следующие базовые классы в одной сборке "Общие":

namespace TestLinq2Sql.Shared
{
    public abstract class UserBase
    {
        public abstract int Id { get; set; }
        public abstract string Name { get; set; }
        public abstract string Email { get; set; }
    }

    public abstract class UserBase<TUser> : UserBase where TUser : UserBase
    {
        public static TUser FindByName_Broken(DataContext db, string name)
        {
            return db.GetTable<TUser>().FirstOrDefault(u => u.Name == name);
        }

        public static TUser FindByName_Works(DataContext db, string name)
        {
            return db.GetTable<TUser>().FirstOrDefault(u => u.Name == name && 1 == 1);
        }

        public static TUser FindByNameEmail_Works(DataContext db, string name, string email)
        {
            return db.GetTable<TUser>().FirstOrDefault(u => u.Name == name || u.Email == email);
        }
    }
}

Эти классы указаны в другой сборке "Main", например:

namespace TestLinq2Sql
{
    partial class User : TestLinq2Sql.Shared.UserBase<User>
    {

    }
}

Файл DBML также находится в сборке "Main".

При вызове User.FindByName_Broken(db, "test") генерируется исключение:

System.InvalidOperationException: член класса UserBase.Name не отображается.

Однако работают два других базовых статических метода.

Кроме того, SQL, сгенерированный вызовом User.FindByName_Works(db, "test"), является тем, на что мы надеялись в сломанном вызове:

SELECT TOP (1) [t0].[Id], [t0].[Name], [t0].[Email]
FROM [dbo].[Users] AS [t0]
WHERE [t0].[Name] = @p0
-- @p0: Input NVarChar (Size = 4; Prec = 0; Scale = 0) [test]

В то время как я готов использовать этот 1 == 1 "взломать" для одиночных запросов предиката, есть ли лучший способ совместного использования кода LINQ to SQL в сборке base/shared/core?

4b9b3361

Ответ 1

Я сталкивался с этой проблемой много раз в прошлом, потому что у нас есть аналогичная архитектура в рамках, которые мы используем в нашей компании. Возможно, вы заметили, что если вы используете запросы LINQ с декларативным стилем, вы не столкнетесь с этой проблемой. Например, следующий код будет работать:

return (from i in db.GetTable<TUser>() where i.Name = "Something").FirstOrDefault();

Однако, поскольку мы используем выражения динамического фильтра, мы не могли использовать этот метод. Альтернативное решение - использовать что-то вроде этого:

return db.GetTable<TUser>().Select(i => i).Where(i => i.Name == "Something").SingleOrDefault();

Это решение решило нашу проблему, так как мы можем ввести ".Select(i = > i)" в начало почти всех выражений. Это заставит механизм Linq не смотреть на базовый класс для сопоставлений и заставит его посмотреть на фактический класс сущности и найти сопоставления.

Надеюсь, что это поможет

Ответ 2

Попробуйте включить параметр OfType before Where

return _dbContext.GetTable<T>().OfType<T>().Where(expression).ToList();

Ответ 3

Мне посчастливилось определять классы данных в общей сборке и потреблять их во многих сборках по сравнению с сопоставлением классов данных сборок в общем контракте. Используя пространство имен примеров, поместите собственный DataContext и ваши общие классы данных в TestLinq2Sql.Shared:

namespace TestLinq2Sql.Shared
{
    public class SharedContext : DataContext
    {
        public Table<User> Users;
        public SharedContext (string connectionString) : base(connectionString) { }
    }

    [Table(Name = "Users")]
    public class User
    {
        [Column(DbType = "Int NOT NULL IDENTITY", IsPrimaryKey=true, CanBeNull = false)]
        public int Id { get; set; }

        [Column(DbType = "nvarchar(40)", CanBeNull = false)]
        public string Name { get; set; }

        [Column(DbType = "nvarchar(100)", CanBeNull = false)]
        public string Email { get; set; }
    }
}

Затем используйте DataContext из любой другой сборки:

using (TestLinq2Sql.Shared.SharedContext shared = 
    new TestLinq2Sql.Shared.SharedContext(
        ConfigurationManager.ConnectionStrings["myConnString"].ConnectionString))
{
    var user = shared.Users.FirstOrDefault(u => u.Name == "test");
}  

Ответ 4

Это выглядит как ошибка - мы особым случаем Single на первичном ключе, чтобы сделать локальный поиск, но похоже, что этот путь кода не правильно захватывает метаданные.

1 = 1 взломать означает, что он идет через обычную базу данных в оба конца, но на самом деле ошибка должна быть подана...

Ответ 5

Вы задаете несколько вопросов здесь, Джаррод, можете быть более конкретным? То есть, вы просто хотите знать, почему ваш метод выходит из строя? Или, может быть, вы хотите использовать объекты данных в разных проектах? Я предполагаю, что вы не пытаетесь использовать LINQ to SQL в качестве слоя сопоставления базы данных и используете его в качестве модели домена? В этом случае оба приложения реализуют один и тот же домен (бизнес-процессы, проверка и т.д.)?